test code v1
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# repo22 手工测试执行记录
|
||||
|
||||
- 执行时间:2026-08-08 10:36-10:38 UTC
|
||||
- 工作区:`/work`
|
||||
- 仓库:`/work/repo-community/repo22`
|
||||
- 执行方式:严格按 Markdown 用例进行前置条件检查;未生成 wrapper 或 CI;未修改代码;未 commit/push
|
||||
- 总结:TC001、TC002、TC007、TC008、TC009、TC010 均为 **BLOCKED**。没有用例进入 FreeBSD 客体内的正式测试步骤,因此没有伪造 PASS/FAIL。
|
||||
|
||||
## 环境版本
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||
uname -a
|
||||
cat /etc/os-release
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
2026-08-08T10:37:36Z
|
||||
Linux da8f2da26d77 6.12.74+deb13+1-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.74-2 (2026-03-08) x86_64 GNU/Linux
|
||||
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
|
||||
VERSION_ID="13"
|
||||
DEBIAN_VERSION_FULL=13.5
|
||||
```
|
||||
|
||||
结论:当前 shell 位于 Linux 容器,不是 FreeBSD;`kldload`、`kldstat`、`mdconfig` 等用例命令不能在此环境代替执行。
|
||||
|
||||
## FreeBSD VM 可访问性
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
timeout 3 bash -c '</dev/tcp/127.0.0.1/9222'
|
||||
ssh -vv -o BatchMode=yes -o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 \
|
||||
-p 9222 [email protected] 'uname -a'
|
||||
ps auxww | grep -E '[q]emu|[b]hyve|[v]mrun|[s]sh.*9222'
|
||||
tmux ls
|
||||
screen -ls
|
||||
```
|
||||
|
||||
实际输出摘要:
|
||||
|
||||
```text
|
||||
/dev/tcp/127.0.0.1/9222: Connection refused
|
||||
CLOSED
|
||||
ssh: connect to host 127.0.0.1 port 9222: Connection refused
|
||||
未发现 qemu/bhyve/vmrun 进程
|
||||
tmux: command not found
|
||||
screen: command not found
|
||||
```
|
||||
|
||||
PID 文件检查:
|
||||
|
||||
```sh
|
||||
cat /work/debug-qemu/local/vm/freebsd-build-runtime.pid
|
||||
ps -p 5274 -o pid,stat,lstart,args
|
||||
cat /work/debug-qemu/local/vm/qemu.pid
|
||||
ps -p 23736 -o pid,stat,lstart,args
|
||||
```
|
||||
|
||||
实际输出:PID 文件分别记录 `5274` 和 `23736`,但 `ps` 对两者都没有返回进程;均为残留 PID 文件。
|
||||
|
||||
## QEMU 与磁盘基线
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
command -v qemu-system-x86_64
|
||||
dpkg-query -W -f='${Status} ${Version}\n' qemu-system-x86 qemu-system-x86_64
|
||||
find /work -type f -name qemu-system-x86_64 -perm /111 -print
|
||||
ls -lh --full-time \
|
||||
/work/debug-qemu/local/vm-freebsd-build.qcow2.bp \
|
||||
/work/debug-qemu/local/vm/freebsd-build-runtime.qcow2
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
command -v: 无输出
|
||||
dpkg-query: no packages found matching qemu-system-x86
|
||||
dpkg-query: no packages found matching qemu-system-x86_64
|
||||
find: 未找到工作区内可执行的 qemu-system-x86_64
|
||||
-r--r--r-- ... 13G ... /work/debug-qemu/local/vm-freebsd-build.qcow2.bp
|
||||
-r--r--r-- ... 15G ... /work/debug-qemu/local/vm/freebsd-build-runtime.qcow2
|
||||
```
|
||||
|
||||
结论:现成 FreeBSD 磁盘基线存在,但宿主机没有 QEMU 可执行文件,无法启动 `/work/debug-qemu/run-freebsd-build-vm.sh`。
|
||||
|
||||
## erofs.ko 盘点
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
find /work -type f \( -name erofs.ko -o -name '*erofs*.ko' \) -printf '%s %p\n' | sort
|
||||
ls -l /work/repo-community/repo22/build/erofs.ko
|
||||
sha256sum /work/debug-qemu/local/erofs.ko /work/build/erofs.ko
|
||||
```
|
||||
|
||||
实际输出摘要:
|
||||
|
||||
```text
|
||||
22784 /work/build/dev-src-check/erofs-kmod-obj/erofs.ko
|
||||
23088 /work/build/dev-src-recheck-obj/erofs-dev-src.ko
|
||||
30480 /work/build/repo11-kmod-obj/erofs.ko
|
||||
30480 /work/build/repo12-erofs.ko
|
||||
30480 /work/build/repo12-kmod-obj/erofs.ko
|
||||
33800 /work/build/erofs-kmod-obj/erofs.ko
|
||||
33800 /work/build/erofs.ko
|
||||
37056 /work/debug-qemu/local/erofs.ko
|
||||
ls: cannot access '/work/repo-community/repo22/build/erofs.ko': No such file or directory
|
||||
246a1ab6cc4e77d88b8ee689339e6565085cd0913f769c91ad1a1ef13d3a3cd9 /work/debug-qemu/local/erofs.ko
|
||||
1fb12045c1fce653cb9f2e75dc76ae3339db6b84def06b92d828357726215f8b /work/build/erofs.ko
|
||||
```
|
||||
|
||||
结论:存在其他历史构建模块,但没有 repo22 的预期产物 `repo22/build/erofs.ko`,不能把来源不明或旧仓库模块当作 repo22 测试对象。
|
||||
|
||||
## 测试镜像盘点
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
for n in test.erofs test-valid.erofs test-bad-crc.erofs \
|
||||
test-bad-magic.erofs test-truncated.erofs test-empty.erofs \
|
||||
test-large.erofs; do
|
||||
find /work -type f -name "$n"
|
||||
done
|
||||
```
|
||||
|
||||
实际输出:七个指定名称均为 0 个文件。
|
||||
|
||||
发现的非指定通用 fixture 包括:
|
||||
|
||||
```text
|
||||
24576 /work/debug-qemu/local/erofs-fixtures/erofs-test.img
|
||||
36864 /work/debug-qemu/local/erofs-fixtures/erofs-plain.img
|
||||
```
|
||||
|
||||
它们不满足 TC009 声明的 10MB/2560 blocks 镜像,也没有证据满足 TC001/TC007 的 `testfile.txt`、TC002 的有效 CRC/损坏 CRC 配对或 TC010 的 48-bit block count,因此未擅自替代。
|
||||
|
||||
## 用例结果
|
||||
|
||||
### TC001-mount-basic — BLOCKED
|
||||
|
||||
- 未执行 `kldload erofs`:当前没有可访问的 FreeBSD 客体。
|
||||
- 未执行后续 mount/umount:缺少已加载 repo22 `erofs.ko` 的 FreeBSD 环境,且缺少 `test.erofs` 与已附加的 `/dev/md0`。
|
||||
- PASS/FAIL:不适用;状态为 BLOCKED。
|
||||
|
||||
### TC002-superblock-crc32c — BLOCKED
|
||||
|
||||
- TC001 前置环境未满足。
|
||||
- 缺少 `test-valid.erofs` 和 `test-bad-crc.erofs`,不能执行有效 CRC 与损坏 CRC 两部分。
|
||||
- PASS/FAIL:不适用;状态为 BLOCKED。
|
||||
|
||||
### TC007-concurrent-mount — BLOCKED
|
||||
|
||||
- TC001 前置环境未满足。
|
||||
- 缺少 `test.erofs`;现有 fixture 未证明包含 `/testfile.txt`,不能按步骤比较两个挂载点的 SHA-256。
|
||||
- PASS/FAIL:不适用;状态为 BLOCKED。
|
||||
|
||||
### TC008-mount-errors — BLOCKED
|
||||
|
||||
- TC001 前置环境未满足。
|
||||
- 缺少 `test-bad-magic.erofs`、`test-truncated.erofs`、`test-empty.erofs`,三个错误分支均无法执行。
|
||||
- PASS/FAIL:不适用;状态为 BLOCKED。
|
||||
|
||||
### TC009-statfs-basic — BLOCKED
|
||||
|
||||
- TC001 前置环境未满足。
|
||||
- 缺少用例声明的 10MB、2560 blocks `test.erofs`,不能验证 `df`/`statfs` 数值。
|
||||
- PASS/FAIL:不适用;状态为 BLOCKED。
|
||||
|
||||
### TC010-statfs-48bit — BLOCKED
|
||||
|
||||
- TC001 前置环境未满足。
|
||||
- 缺少带 48-bit block count、块数大于 `2^32` 的 `test-large.erofs`。
|
||||
- PASS/FAIL:不适用;状态为 BLOCKED。
|
||||
|
||||
## 精确解阻条件
|
||||
|
||||
1. 提供可访问的 FreeBSD 13.0+ VM;最直接方式是恢复 `127.0.0.1:9222` SSH,或在宿主机安装可用的 `qemu-system-x86_64` 后启动现有基线。
|
||||
2. 提供由当前 repo22 源码构建、与客体 FreeBSD ABI 匹配的 `erofs.ko`。
|
||||
3. 提供并确认以下镜像及内容/元数据:`test.erofs`、`test-valid.erofs`、`test-bad-crc.erofs`、`test-bad-magic.erofs`、`test-truncated.erofs`、`test-empty.erofs`、`test-large.erofs`。
|
||||
4. TC001/TC007 的基本镜像需包含 `/testfile.txt`;TC009 的镜像需为约 10MB、2560 blocks;TC010 的镜像需具有可验证的 48-bit block count。
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
# repo22 手工测试报告
|
||||
|
||||
- 执行日期:2026-08-08(UTC)
|
||||
- 执行时段:2026-08-08T11:08Z 至 2026-08-08T11:14:44Z
|
||||
- 工作区:`/work`
|
||||
- 测试对象:`/work/repo-community/repo22`
|
||||
- ABI 提交:`f2caaae2890a910d0f11547d9ff5f7cf92c062a2`
|
||||
- 提交时间:`2026-08-08T11:12:36Z`
|
||||
- 提交主题:`repo22: align core on-disk structures`
|
||||
- 执行方式:手工 SSH/SCP、手工模块命令;未使用测试 wrapper 或 CI;未修改源码;未 commit/push
|
||||
- 总体结论:**FAIL / BLOCKED**
|
||||
|
||||
## 结果摘要
|
||||
|
||||
| 项目 | 结果 | 证据 |
|
||||
|---|---|---|
|
||||
| SSH 与 guest 环境 | PASS | FreeBSD 15.0-RELEASE-p8 amd64,root,securelevel -1,所需系统工具存在 |
|
||||
| 等待 repo22 ABI 提交 | PASS | `f2caaae28` 出现后 repo22 scoped 工作树干净 |
|
||||
| 宿主构建 repo22 `erofs.ko` | PASS | 构建退出 0,产物 45088 字节 |
|
||||
| SCP 到 guest | PASS | 宿主/guest SHA-256 均为 `f119bb2c902bf6e221b532b1c8e7668caaa5c744c46b4eac8f9f105cd7494e9f` |
|
||||
| guest `kldload` | **FAIL** | 返回 1;dmesg:`link_elf_obj: symbol bcmp undefined` |
|
||||
| guest `kldstat`(加载后) | BLOCKED | `kldload` 未成功,不能出现已加载模块 |
|
||||
| guest `kldunload` | BLOCKED | 模块从未加载,不能执行有效卸载验证 |
|
||||
| 制作/复制 plain 镜像 | NOT RUN | 按“任何步骤失败立即停止”要求,在模块加载失败后停止 |
|
||||
| TC001-mount-basic | BLOCKED / NOT RUN | 前置条件“EROFS 模块已加载”不满足 |
|
||||
| TC009-statfs-basic | BLOCKED / NOT RUN | 仅在 TC001 通过后执行;TC001 未执行 |
|
||||
|
||||
## 1. 初始 SSH 验证
|
||||
|
||||
首次按题目给出的普通 SSH 方式验证,端口可达,但 guest 不接受宿主默认公钥。
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
|
||||
-o ConnectTimeout=8 -p 9222 [email protected] \
|
||||
'echo "== identity =="; id; echo "== uname =="; uname -a'
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
Warning: Permanently added '[127.0.0.1]:9222' (ED25519) to the list of known hosts.
|
||||
[email protected]: Permission denied (publickey,keyboard-interactive).
|
||||
```
|
||||
|
||||
检查现有 VM 基线文档后,确认该 guest 使用固定测试密码和
|
||||
`keyboard-interactive,password`。宿主没有 `sshpass`,因此创建了仅用于 SSH/SCP
|
||||
认证的临时 `/tmp/repo22-ssh-askpass.sh`;它不是测试 wrapper,未写入仓库。
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
cat > /tmp/repo22-ssh-askpass.sh <<'EOF'
|
||||
#!/bin/sh
|
||||
printf '%s\n' '<固定测试密码>'
|
||||
EOF
|
||||
chmod 700 /tmp/repo22-ssh-askpass.sh
|
||||
```
|
||||
|
||||
随后所有 SSH/SCP 命令使用以下认证前缀和选项:
|
||||
|
||||
```sh
|
||||
DISPLAY=:0 SSH_ASKPASS=/tmp/repo22-ssh-askpass.sh \
|
||||
SSH_ASKPASS_REQUIRE=force setsid -w ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=keyboard-interactive,password \
|
||||
-o PubkeyAuthentication=no \
|
||||
-p 9222 [email protected] '<guest command>'
|
||||
```
|
||||
|
||||
## 2. Guest 环境验证
|
||||
|
||||
实际 guest 命令:
|
||||
|
||||
```sh
|
||||
echo "== UTC date =="; date -u "+%Y-%m-%dT%H:%M:%SZ"
|
||||
echo "== identity =="; id
|
||||
echo "== uname =="; uname -a
|
||||
echo "== freebsd-version =="; freebsd-version -ku 2>&1
|
||||
echo "== hostname =="; hostname
|
||||
echo "== architecture =="; uname -m; sysctl -n hw.machine_arch 2>&1
|
||||
echo "== securelevel =="; sysctl kern.securelevel
|
||||
echo "== module tools =="; command -v kldload; command -v kldstat; command -v kldunload
|
||||
echo "== filesystem tools =="; command -v mdconfig; command -v mount; command -v umount; command -v sha256; command -v stat
|
||||
echo "== existing erofs module/mount/md =="; kldstat | grep -i erofs || true; mount | grep -i erofs || true; mdconfig -l
|
||||
echo "== temp space =="; df -h /tmp /root
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
Warning: Permanently added '[127.0.0.1]:9222' (ED25519) to the list of known hosts.
|
||||
== UTC date ==
|
||||
2026-08-08T11:10:13Z
|
||||
== identity ==
|
||||
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
|
||||
== uname ==
|
||||
FreeBSD freebsd-build 15.0-RELEASE-p8 FreeBSD 15.0-RELEASE-p8 releng/15.0-n281036-53054229dcb3 GENERIC amd64
|
||||
== freebsd-version ==
|
||||
15.0-RELEASE-p8
|
||||
15.0-RELEASE-p8
|
||||
== hostname ==
|
||||
freebsd-build
|
||||
== architecture ==
|
||||
amd64
|
||||
amd64
|
||||
== securelevel ==
|
||||
kern.securelevel: -1
|
||||
== module tools ==
|
||||
/sbin/kldload
|
||||
/sbin/kldstat
|
||||
/sbin/kldunload
|
||||
== filesystem tools ==
|
||||
/sbin/mdconfig
|
||||
/sbin/mount
|
||||
/sbin/umount
|
||||
/sbin/sha256
|
||||
/usr/bin/stat
|
||||
== existing erofs module/mount/md ==
|
||||
== temp space ==
|
||||
Filesystem Size Used Avail Capacity Mounted on
|
||||
/dev/vtbd0p2 112G 12G 91G 12% /
|
||||
/dev/vtbd0p2 112G 12G 91G 12% /
|
||||
```
|
||||
|
||||
结论:guest 连接及基础环境 PASS;开始时没有已加载 EROFS 模块、EROFS 挂载或 md 设备。
|
||||
|
||||
## 3. 等待 ABI 提交
|
||||
|
||||
轮询条件:HEAD 必须晚于测试开始时的 `db524d69f`,且
|
||||
`git status --porcelain -- repo-community/repo22` 行数必须为 0。
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
baseline=db524d69f
|
||||
for attempt in $(seq 1 40); do
|
||||
now=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
head=$(git rev-parse --short HEAD)
|
||||
subject=$(git log -1 --pretty=%s)
|
||||
dirty=$(git status --porcelain -- repo-community/repo22 | wc -l)
|
||||
printf '%s attempt=%02d HEAD=%s dirty_repo22=%s subject=%s\n' \
|
||||
"$now" "$attempt" "$head" "$dirty" "$subject"
|
||||
if [ "$head" != "$baseline" ] && [ "$dirty" -eq 0 ]; then
|
||||
echo 'READY: repo22 has a newer commit and its scoped worktree is clean'
|
||||
git log --date=iso-strict -5 --pretty=format:'%h %ad %an %s'
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
2026-08-08T11:10:33Z attempt=01 HEAD=db524d69f dirty_repo22=6 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:10:49Z attempt=02 HEAD=db524d69f dirty_repo22=6 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:11:04Z attempt=03 HEAD=db524d69f dirty_repo22=6 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:11:20Z attempt=04 HEAD=db524d69f dirty_repo22=6 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:11:35Z attempt=05 HEAD=db524d69f dirty_repo22=19 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:11:50Z attempt=06 HEAD=db524d69f dirty_repo22=19 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:12:05Z attempt=07 HEAD=db524d69f dirty_repo22=19 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:12:21Z attempt=08 HEAD=db524d69f dirty_repo22=19 subject=repo22: record initial manual test blockers
|
||||
2026-08-08T11:12:36Z attempt=09 HEAD=f2caaae28 dirty_repo22=0 subject=repo22: align core on-disk structures
|
||||
READY: repo22 has a newer commit and its scoped worktree is clean
|
||||
f2caaae28 2026-08-08T11:12:36Z Ruicheng Pan repo22: align core on-disk structures
|
||||
db524d69f 2026-08-08T10:46:37Z Ruicheng Pan repo22: record initial manual test blockers
|
||||
524dcfac5 2026-08-08T10:43:55Z Ruicheng Pan repo22: harden core I/O and build path
|
||||
173385f79 2026-08-08T10:09:38Z Ruicheng Pan Add repo22 based on repo21 for comprehensive validation and review
|
||||
c782c1dc8 2026-06-18T20:01:35Z Ruicheng Pan 补充和完善 repo21_explain/src 中文注释
|
||||
```
|
||||
|
||||
## 4. 宿主构建 repo22 erofs.ko
|
||||
|
||||
实际命令(工作目录 `/work/repo-community/repo22`):
|
||||
|
||||
```sh
|
||||
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||
git rev-parse HEAD
|
||||
clang --version | head -1
|
||||
FREEBSD_SRC=/work/dev-freebsd-releng ./build.sh
|
||||
echo "build_exit=$?"
|
||||
ls -l --full-time build/erofs.ko
|
||||
sha256sum build/erofs.ko
|
||||
```
|
||||
|
||||
实际输出(编译器对 FreeBSD 内核头文件产生了多组重复的 builtin redeclaration
|
||||
warning;构建未使用 `-Werror`,没有编译或链接 error):
|
||||
|
||||
```text
|
||||
2026-08-08T11:13:13Z
|
||||
f2caaae2890a910d0f11547d9ff5f7cf92c062a2
|
||||
Debian clang version 19.1.7 (3+b1)
|
||||
==> Building repo22 erofs.ko
|
||||
[data.c、dir.c、erofs_vnops.c、inode.c、namei.c、super.c、xattr.c、lz4.c、deflate.c、zstd.c、lzma.c 编译期间重复出现以下 warning:]
|
||||
warning: incompatible redeclaration of library function 'log' [-Wincompatible-library-redeclaration]
|
||||
warning: incompatible redeclaration of library function 'strdup' [-Wincompatible-library-redeclaration]
|
||||
warning: incompatible redeclaration of library function 'strndup' [-Wincompatible-library-redeclaration]
|
||||
warning: incompatible redeclaration of library function 'free' [-Wincompatible-library-redeclaration]
|
||||
warning: incompatible redeclaration of library function 'malloc' [-Wincompatible-library-redeclaration]
|
||||
warning: incompatible redeclaration of library function 'realloc' [-Wincompatible-library-redeclaration]
|
||||
/work/repo-community/repo22/src/lzma.c:133:19: warning: result of comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always true [-Wtautological-constant-out-of-range-compare]
|
||||
/work/repo-community/repo22/src/lzma.c:135:16: warning: result of comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always true [-Wtautological-constant-out-of-range-compare]
|
||||
==> SUCCESS: /work/repo-community/repo22/build/erofs.ko
|
||||
build_exit=0
|
||||
-rw-r--r-- 1 root root 45088 2026-08-08 11:13:16.704187568 +0000 build/erofs.ko
|
||||
f119bb2c902bf6e221b532b1c8e7668caaa5c744c46b4eac8f9f105cd7494e9f build/erofs.ko
|
||||
```
|
||||
|
||||
说明:原始构建输出包含每个源文件的同类完整 warning 展开;本报告逐类保留了实际
|
||||
warning 文本和两个非重复的 `lzma.c` warning,并保留了完整的成功、退出码、尺寸和哈希证据。
|
||||
|
||||
## 5. SCP 模块到 guest
|
||||
|
||||
实际命令(密码由临时 askpass 提供):
|
||||
|
||||
```sh
|
||||
DISPLAY=:0 SSH_ASKPASS=/tmp/repo22-ssh-askpass.sh \
|
||||
SSH_ASKPASS_REQUIRE=force setsid -w scp -O \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=keyboard-interactive,password \
|
||||
-o PubkeyAuthentication=no \
|
||||
-P 9222 build/erofs.ko [email protected]:/root/repo22-erofs.ko
|
||||
echo "scp_exit=$?"
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
Warning: Permanently added '[127.0.0.1]:9222' (ED25519) to the list of known hosts.
|
||||
scp_exit=0
|
||||
```
|
||||
|
||||
guest 校验命令:
|
||||
|
||||
```sh
|
||||
ls -l /root/repo22-erofs.ko
|
||||
sha256 /root/repo22-erofs.ko
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
-rw-r--r-- 1 root wheel 45088 Aug 8 11:13 /root/repo22-erofs.ko
|
||||
SHA256 (/root/repo22-erofs.ko) = f119bb2c902bf6e221b532b1c8e7668caaa5c744c46b4eac8f9f105cd7494e9f
|
||||
```
|
||||
|
||||
结论:复制成功且字节身份一致。
|
||||
|
||||
## 6. Guest 手工模块加载测试
|
||||
|
||||
### 6.1 加载前状态与 dmesg
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
kldstat | grep -i erofs
|
||||
echo "pre_kldstat_grep_exit=$?"
|
||||
dmesg | tail -30
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
pre_kldstat_grep_exit=1
|
||||
device_attach: fdc0 attach returned 6
|
||||
ppc0: <Parallel port> port 0x378-0x37f irq 7 on acpi0
|
||||
ppc0: Generic chipset (NIBBLE-only) in COMPATIBLE mode
|
||||
ppbus0: <Parallel port bus> on ppc0
|
||||
lpt0: <Printer> on ppbus0
|
||||
lpt0: Interrupt-driven port
|
||||
ppi0: <Parallel I/O> on ppbus0
|
||||
uart0: <16550 or compatible> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0
|
||||
orm0: <ISA Option ROM> at iomem 0xe7800-0xeffff pnpid ORM0000 on isa0
|
||||
vga0: <Generic ISA VGA> at port 0x3c0-0x3df iomem 0xa0000-0xbffff pnpid PNP0900 on isa0
|
||||
attimer0: <AT timer> at port 0x40 on isa0
|
||||
Timecounter "i8254" frequency 1193182 Hz quality 0
|
||||
Event timer "i8254" frequency 1193182 Hz quality 100
|
||||
attimer0: non-PNP ISA device will be removed from GENERIC in FreeBSD 16.
|
||||
Timecounters tick every 10.000 msec
|
||||
usb_needs_explore_all: no devclass
|
||||
Trying to mount root from ufs:/dev/vtbd0p2 [rw]...
|
||||
WARNING: / was not properly dismounted
|
||||
WARNING: /: mount pending error: blocks 0 files 32
|
||||
cd0 at ata1 bus 0 scbus1 target 0 lun 0
|
||||
cd0: <QEMU QEMU DVD-ROM 2.5+> Removable CD-ROM SCSI device
|
||||
cd0: Serial Number QM00003
|
||||
cd0: 16.700MB/s transfers (WDMA2, ATAPI 12bytes, PIO 65534bytes)
|
||||
cd0: Attempt to query device size failed: NOT READY, Medium not present
|
||||
intsmb0: <Intel PIIX4 SMBUS Interface> irq 9 at device 1.3 on pci0
|
||||
intsmb0: intr IRQ 9 enabled revision 0
|
||||
smbus0: <System Management Bus> on intsmb0
|
||||
lo0: link state changed to UP
|
||||
vtnet0: link state changed to UP
|
||||
Security policy loaded: MAC/ntpd (mac_ntpd)
|
||||
```
|
||||
|
||||
### 6.2 kldload
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
kldload /root/repo22-erofs.ko
|
||||
echo "kldload_exit=$?"
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
kldload: an error occurred while loading module /root/repo22-erofs.ko. Please check dmesg(8) for more details.
|
||||
kldload_exit=1
|
||||
```
|
||||
|
||||
失败后的实际 dmesg 命令:
|
||||
|
||||
```sh
|
||||
dmesg | tail -80
|
||||
```
|
||||
|
||||
实际输出末尾(失败相关行):
|
||||
|
||||
```text
|
||||
link_elf_obj: symbol bcmp undefined
|
||||
linker_load_file: /root/repo22-erofs.ko - unsupported file type
|
||||
```
|
||||
|
||||
最终复核命令:
|
||||
|
||||
```sh
|
||||
date -u "+%Y-%m-%dT%H:%M:%SZ"
|
||||
kldstat | grep -i erofs
|
||||
echo "kldstat_grep_exit=$?"
|
||||
mount | grep -i erofs
|
||||
echo "mount_grep_exit=$?"
|
||||
mdconfig -l
|
||||
dmesg | grep -E "bcmp|repo22-erofs|linker_load_file" | tail -20
|
||||
ls -l /root/repo22-erofs.ko
|
||||
sha256 /root/repo22-erofs.ko
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
2026-08-08T11:14:44Z
|
||||
kldstat_grep_exit=1
|
||||
mount_grep_exit=1
|
||||
link_elf_obj: symbol bcmp undefined
|
||||
linker_load_file: /root/repo22-erofs.ko - unsupported file type
|
||||
-rw-r--r-- 1 root wheel 45088 Aug 8 11:13 /root/repo22-erofs.ko
|
||||
SHA256 (/root/repo22-erofs.ko) = f119bb2c902bf6e221b532b1c8e7668caaa5c744c46b4eac8f9f105cd7494e9f
|
||||
```
|
||||
|
||||
结论:**模块加载 FAIL**。失败不是 SCP 损坏导致,因为宿主和 guest 哈希完全一致。
|
||||
FreeBSD 内核链接器无法解析模块引用的 `bcmp` 符号。
|
||||
|
||||
### 6.3 kldstat 与 kldunload
|
||||
|
||||
- 加载后的 `kldstat` 验证:**BLOCKED**。加载操作返回 1,最终复核也确认没有 EROFS 模块。
|
||||
- `kldunload erofs`:**BLOCKED / NOT RUN**。模块从未成功加载;执行卸载不能构成有效卸载测试。
|
||||
- 已按“任何步骤失败立即记录并停止”要求停止,没有尝试替换模块、修改 guest、绕过未解析符号或继续文件系统测试。
|
||||
|
||||
## 7. mkfs.erofs 与 plain 镜像
|
||||
|
||||
宿主工具前置检查实际命令:
|
||||
|
||||
```sh
|
||||
command -v mkfs.erofs
|
||||
mkfs.erofs -V
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
/usr/bin/mkfs.erofs
|
||||
mkfs.erofs (erofs-utils) 1.8.6
|
||||
available compressors: lz4, lz4hc, lzma, deflate, libdeflate, zstd
|
||||
```
|
||||
|
||||
状态:**NOT RUN**。工具版本满足要求,但 `kldload` 已失败,因此没有生成源数据、
|
||||
没有生成 plain 镜像、没有复制镜像到 guest。这样避免在首个失败后继续推进并产生误导性结果。
|
||||
|
||||
## 8. TC001-mount-basic
|
||||
|
||||
状态:**BLOCKED / NOT RUN**。
|
||||
|
||||
阻塞原因:`TC001-mount-basic.md` 的前置条件要求 FreeBSD 系统已加载 EROFS 内核模块;
|
||||
本轮 `kldload /root/repo22-erofs.ko` 返回 1。因而以下命令均未执行:
|
||||
|
||||
```sh
|
||||
mdconfig -a -t vnode -f test.erofs -u 0
|
||||
mkdir -p /mnt/test
|
||||
mount -t erofs /dev/md0 /mnt/test
|
||||
mount | grep erofs
|
||||
df -h /mnt/test
|
||||
sha256 /mnt/test/<测试文件>
|
||||
umount /mnt/test
|
||||
mdconfig -d -u md0
|
||||
```
|
||||
|
||||
没有把 BLOCKED 记录为 PASS 或文件系统行为 FAIL。
|
||||
|
||||
## 9. TC009-statfs-basic
|
||||
|
||||
状态:**BLOCKED / NOT RUN**。
|
||||
|
||||
用户要求仅在 TC001 通过后执行 TC009。TC001 因模块加载失败没有进入测试步骤,故
|
||||
`df -h`、`df -i`、`stat -f` 和只读标志检查均未执行。
|
||||
|
||||
## 10. 工作树与提交状态
|
||||
|
||||
ABI 提交完成后的实际确认:
|
||||
|
||||
```text
|
||||
HEAD=f2caaae2890a910d0f11547d9ff5f7cf92c062a2
|
||||
git status --porcelain -- repo-community/repo22
|
||||
<无输出>
|
||||
```
|
||||
|
||||
本报告是本轮唯一预期新增文件。未修改 repo22 源码,未 commit,未 push。
|
||||
|
||||
## 最终判定
|
||||
|
||||
- 手工测试准备环境:PASS
|
||||
- repo22 模块宿主构建:PASS
|
||||
- 模块传输与完整性:PASS
|
||||
- FreeBSD guest 模块加载:**FAIL**
|
||||
- 失败根因证据:`link_elf_obj: symbol bcmp undefined`
|
||||
- TC001:BLOCKED / NOT RUN
|
||||
- TC009:BLOCKED / NOT RUN
|
||||
- 总体:**FAIL / BLOCKED**
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
# repo22 手工基础测试报告
|
||||
|
||||
- 执行时间:2026-08-08 11:38-11:43 UTC
|
||||
- 工作区:`/work`
|
||||
- repo22:`/work/repo-community/repo22`
|
||||
- Git 分支:`main`
|
||||
- 测试源码提交:`0c173fa5c07a0c2af7dafcf8f89f0167ae3e1f9d`
|
||||
- FreeBSD VM:SSH `127.0.0.1:9222`
|
||||
- 执行方式:手工构建、SSH/SCP 和逐条 shell 命令;未生成测试 wrapper 或 CI;未修改源码
|
||||
- 前一轮失败报告:保留 `tests/results/manual/2026-08-08T1114Z/manual-test-report.md`
|
||||
- 总体结论:**TC001 PASS;TC009 FAIL(用例中的 GNU 风格 `stat` 命令与 FreeBSD `stat(1)` 不兼容)**
|
||||
|
||||
## 结果摘要
|
||||
|
||||
| 项目 | 状态 | 实际结果 |
|
||||
|---|---|---|
|
||||
| repo22 模块重建 | PASS | `build/erofs.ko`,41712 字节 |
|
||||
| `bcmp` 构建检查 | PASS | `nm -u` 中没有 `bcmp` |
|
||||
| 模块传输完整性 | PASS | host/guest SHA-256 均为 `6c13819ee36b1f7fe62ea084640fdefb2e871f245462291ce4c18f1ee93a684f` |
|
||||
| `kldload` / `kldstat` / `kldunload` | PASS | canonical `/root/erofs.ko` 三步退出码均为 0 |
|
||||
| 固定 plain 镜像 | PASS | 10 MiB、2560×4096 blocks、重复构建逐字节相同 |
|
||||
| TC001-mount-basic | **PASS** | 挂载、只读标志、容量、读取、卸载均符合预期 |
|
||||
| TC009-statfs-basic | **FAIL** | `df`/只读/容量正确;原文步骤 3 未显示文件系统信息,步骤 4 返回 1 |
|
||||
| 清理 | PASS | 无 EROFS 模块、挂载和 md 设备残留;guest 临时文件已删除 |
|
||||
|
||||
## 1. Host 环境与源码状态
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||
uname -a
|
||||
cat /etc/os-release
|
||||
git rev-parse HEAD
|
||||
git branch --show-current
|
||||
git show HEAD:repo-community/repo22/build.sh | sha256sum
|
||||
sha256sum repo-community/repo22/build.sh
|
||||
git diff -- repo-community/repo22/src repo-community/repo22/build.sh
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
2026-08-08T11:38:49Z
|
||||
Linux da8f2da26d77 6.12.74+deb13+1-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.74-2 (2026-03-08) x86_64 GNU/Linux
|
||||
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
|
||||
VERSION_ID="13"
|
||||
DEBIAN_VERSION_FULL=13.5
|
||||
0c173fa5c07a0c2af7dafcf8f89f0167ae3e1f9d
|
||||
main
|
||||
70b6c015b33479c271e11abf36108c7f9e7ca9e2b9aa9d931fe7ae21f27f59ff -
|
||||
70b6c015b33479c271e11abf36108c7f9e7ca9e2b9aa9d931fe7ae21f27f59ff repo-community/repo22/build.sh
|
||||
<git diff 无输出>
|
||||
```
|
||||
|
||||
## 2. 重新构建模块与 `bcmp` 检查
|
||||
|
||||
实际命令:
|
||||
|
||||
```sh
|
||||
cd /work/repo-community/repo22
|
||||
FREEBSD_SRC=/work/dev-freebsd-releng ./build.sh
|
||||
ls -l --full-time build/erofs.ko
|
||||
sha256sum build/erofs.ko
|
||||
nm -u build/erofs.ko | awk '$NF == "bcmp" {print; found=1} END {if (!found) print "none"}'
|
||||
```
|
||||
|
||||
实际输出:
|
||||
|
||||
```text
|
||||
==> Building repo22 erofs.ko
|
||||
/work/repo-community/repo22/src/lzma.c:133:19: warning: result of comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always true [-Wtautological-constant-out-of-range-compare]
|
||||
133 | } while (byte < 0x100);
|
||||
| ~~~~ ^ ~~~~~
|
||||
/work/repo-community/repo22/src/lzma.c:135:16: warning: result of comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always true [-Wtautological-constant-out-of-range-compare]
|
||||
135 | while (byte < 0x100)
|
||||
| ~~~~ ^ ~~~~~
|
||||
2 warnings generated.
|
||||
==> SUCCESS: /work/repo-community/repo22/build/erofs.ko
|
||||
-rw-r--r-- 1 root root 41712 2026-08-08 11:38:52.056076393 +0000 build/erofs.ko
|
||||
6c13819ee36b1f7fe62ea084640fdefb2e871f245462291ce4c18f1ee93a684f build/erofs.ko
|
||||
none
|
||||
```
|
||||
|
||||
结论:构建退出 0,上一轮导致 guest 加载失败的未解析 `bcmp` 已不在模块中。
|
||||
|
||||
## 3. SSH 与 Guest 基线
|
||||
|
||||
普通公钥探测:
|
||||
|
||||
```sh
|
||||
timeout 5 bash -c '</dev/tcp/127.0.0.1/9222'
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \
|
||||
-p 9222 [email protected] 'date -u; uname -a; id; kldstat'
|
||||
```
|
||||
|
||||
```text
|
||||
PORT_OPEN
|
||||
[email protected]: Permission denied (publickey,keyboard-interactive).
|
||||
```
|
||||
|
||||
按现有 VM helper 的固定密码认证方式继续。密码脱敏;临时 askpass 文件位于 `/tmp`,不是测试 wrapper,未加入仓库。
|
||||
|
||||
```sh
|
||||
cat > /tmp/repo22-ssh-askpass.sh <<'ASKPASS'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$REPO22_VM_PASS"
|
||||
ASKPASS
|
||||
chmod 700 /tmp/repo22-ssh-askpass.sh
|
||||
export REPO22_VM_PASS='<redacted>'
|
||||
export DISPLAY=:0 SSH_ASKPASS=/tmp/repo22-ssh-askpass.sh SSH_ASKPASS_REQUIRE=force
|
||||
setsid -w ssh \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=keyboard-interactive,password \
|
||||
-o PubkeyAuthentication=no -p 9222 [email protected] '
|
||||
date -u "+%Y-%m-%dT%H:%M:%SZ"
|
||||
uname -a
|
||||
freebsd-version -ku
|
||||
id
|
||||
sysctl kern.securelevel
|
||||
command -v kldload; command -v kldstat; command -v kldunload
|
||||
command -v mdconfig; command -v mount; command -v umount
|
||||
command -v stat; command -v sha256
|
||||
kldstat | grep -i erofs; echo "kldstat_erofs_exit=$?"
|
||||
mount | grep -i erofs; echo "mount_erofs_exit=$?"
|
||||
mdconfig -l
|
||||
'
|
||||
```
|
||||
|
||||
```text
|
||||
2026-08-08T11:39:48Z
|
||||
FreeBSD freebsd-build 15.0-RELEASE-p8 FreeBSD 15.0-RELEASE-p8 releng/15.0-n281036-53054229dcb3 GENERIC amd64
|
||||
15.0-RELEASE-p8
|
||||
15.0-RELEASE-p8
|
||||
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
|
||||
kern.securelevel: -1
|
||||
/sbin/kldload
|
||||
/sbin/kldstat
|
||||
/sbin/kldunload
|
||||
/sbin/mdconfig
|
||||
/sbin/mount
|
||||
/sbin/umount
|
||||
/usr/bin/stat
|
||||
/sbin/sha256
|
||||
kldstat_erofs_exit=1
|
||||
mount_erofs_exit=1
|
||||
<mdconfig -l 无输出>
|
||||
```
|
||||
|
||||
结论:VM 是 FreeBSD 15.0-RELEASE-p8 amd64;初始无 EROFS 模块、挂载或 md 设备。
|
||||
|
||||
## 4. 模块复制与生命周期回归
|
||||
|
||||
```sh
|
||||
setsid -w scp -O \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=keyboard-interactive,password \
|
||||
-o PubkeyAuthentication=no -P 9222 \
|
||||
repo-community/repo22/build/erofs.ko \
|
||||
[email protected]:/root/repo22-erofs.ko
|
||||
```
|
||||
|
||||
首次使用非 canonical 文件名加载的实际命令与输出:
|
||||
|
||||
```sh
|
||||
ls -l /root/repo22-erofs.ko
|
||||
sha256 /root/repo22-erofs.ko
|
||||
dmesg | grep -E "bcmp|repo22-erofs|linker_load_file|erofs" | tail -20
|
||||
kldload /root/repo22-erofs.ko; echo "kldload_exit=$?"
|
||||
kldstat | grep -i erofs; echo "kldstat_grep_exit=$?"
|
||||
dmesg | grep -E "bcmp|repo22-erofs|linker_load_file|erofs" | tail -20
|
||||
kldunload erofs; echo "kldunload_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
-rw-r--r-- 1 root wheel 41712 Aug 8 11:40 /root/repo22-erofs.ko
|
||||
SHA256 (/root/repo22-erofs.ko) = 6c13819ee36b1f7fe62ea084640fdefb2e871f245462291ce4c18f1ee93a684f
|
||||
link_elf_obj: symbol bcmp undefined
|
||||
linker_load_file: /root/repo22-erofs.ko - unsupported file type
|
||||
kldload_exit=0
|
||||
5 1 0xffffffff82822000 5d30 repo22-erofs.ko
|
||||
kldstat_grep_exit=0
|
||||
link_elf_obj: symbol bcmp undefined
|
||||
linker_load_file: /root/repo22-erofs.ko - unsupported file type
|
||||
kldunload: can't find file erofs
|
||||
kldunload_exit=1
|
||||
```
|
||||
|
||||
两条 `bcmp`/`unsupported file type` 是本轮加载前已存在的上一轮 dmesg 历史行;加载前后输出相同,本轮 `kldload_exit=0`,没有新增链接错误。首次卸载失败仅因加载文件名是 `repo22-erofs.ko`。
|
||||
|
||||
按模块 ID 清理后,用 canonical 文件名重新执行完整生命周期:
|
||||
|
||||
```sh
|
||||
module_id=$(kldstat | awk '/repo22-erofs[.]ko/ {print $1}')
|
||||
echo "module_id=$module_id"
|
||||
kldunload -i "$module_id"; echo "kldunload_by_id_exit=$?"
|
||||
kldstat | grep -i erofs; echo "post_id_unload_kldstat_exit=$?"
|
||||
cp -f /root/repo22-erofs.ko /root/erofs.ko
|
||||
sha256 /root/erofs.ko
|
||||
kldload /root/erofs.ko; echo "kldload_exit=$?"
|
||||
kldstat | grep -i erofs; echo "kldstat_grep_exit=$?"
|
||||
kldunload erofs; echo "kldunload_exit=$?"
|
||||
kldstat | grep -i erofs; echo "post_unload_kldstat_grep_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
module_id=5
|
||||
kldunload_by_id_exit=0
|
||||
post_id_unload_kldstat_exit=1
|
||||
SHA256 (/root/erofs.ko) = 6c13819ee36b1f7fe62ea084640fdefb2e871f245462291ce4c18f1ee93a684f
|
||||
kldload_exit=0
|
||||
5 1 0xffffffff82822000 5d30 erofs.ko
|
||||
kldstat_grep_exit=0
|
||||
kldunload_exit=0
|
||||
post_unload_kldstat_grep_exit=1
|
||||
```
|
||||
|
||||
结论:`bcmp` 修复后的 `kldload`、`kldstat`、`kldunload` 回归 **PASS**。
|
||||
|
||||
## 5. 固定 plain 镜像
|
||||
|
||||
工具检查:
|
||||
|
||||
```sh
|
||||
command -v mkfs.erofs
|
||||
mkfs.erofs -V
|
||||
command -v dump.erofs
|
||||
dump.erofs -V
|
||||
```
|
||||
|
||||
```text
|
||||
/usr/bin/mkfs.erofs
|
||||
mkfs.erofs (erofs-utils) 1.8.6
|
||||
available compressors: lz4, lz4hc, lzma, deflate, libdeflate, zstd
|
||||
/usr/bin/dump.erofs
|
||||
dump.erofs (erofs-utils) 1.8.6
|
||||
```
|
||||
|
||||
首次尝试显式禁用 inline:
|
||||
|
||||
```sh
|
||||
mkfs.erofs -b4096 -x-1 -T0 --all-root --ignore-mtime \
|
||||
-Enoinline -L repo22-manual \
|
||||
/tmp/repo22-manual-plain/test.erofs \
|
||||
/tmp/repo22-manual-plain/src
|
||||
```
|
||||
|
||||
```text
|
||||
<E> erofs: unknown extended option noinline
|
||||
Try 'mkfs.erofs --help' for more information.
|
||||
mkfs.erofs 1.8.6
|
||||
```
|
||||
|
||||
该命令退出 1 且未产出镜像。随后用整块大小、无压缩的主文件,并固定时间戳、UUID、label、属主和 xattr 设置:
|
||||
|
||||
```sh
|
||||
rm -rf /tmp/repo22-manual-plain
|
||||
mkdir -p /tmp/repo22-manual-plain/src
|
||||
printf 'repo22 FreeBSD EROFS manual test\n' > /tmp/repo22-manual-plain/src/README.txt
|
||||
dd if=/dev/zero of=/tmp/repo22-manual-plain/src/plain.bin bs=4096 count=2559 status=none
|
||||
touch -d @0 /tmp/repo22-manual-plain/src \
|
||||
/tmp/repo22-manual-plain/src/README.txt \
|
||||
/tmp/repo22-manual-plain/src/plain.bin
|
||||
mkfs.erofs -b4096 -x-1 -T0 --all-root --ignore-mtime \
|
||||
-U 00000000-0000-0000-0000-000000000022 -L repo22-manual \
|
||||
/tmp/repo22-manual-plain/test.erofs /tmp/repo22-manual-plain/src
|
||||
mkfs.erofs -b4096 -x-1 -T0 --all-root --ignore-mtime \
|
||||
-U 00000000-0000-0000-0000-000000000022 -L repo22-manual \
|
||||
/tmp/repo22-manual-plain/test-repeat.erofs /tmp/repo22-manual-plain/src
|
||||
cmp /tmp/repo22-manual-plain/test.erofs /tmp/repo22-manual-plain/test-repeat.erofs
|
||||
echo "cmp_exit=$?"
|
||||
sha256sum /tmp/repo22-manual-plain/src/README.txt \
|
||||
/tmp/repo22-manual-plain/src/plain.bin \
|
||||
/tmp/repo22-manual-plain/test.erofs \
|
||||
/tmp/repo22-manual-plain/test-repeat.erofs
|
||||
dump.erofs -s /tmp/repo22-manual-plain/test.erofs
|
||||
```
|
||||
|
||||
关键实际输出:
|
||||
|
||||
```text
|
||||
Filesystem UUID: 00000000-0000-0000-0000-000000000022
|
||||
Filesystem total blocks: 2560 (of 4096-byte blocks)
|
||||
Filesystem total inodes: 3
|
||||
Filesystem total metadata blocks: 1
|
||||
cmp_exit=0
|
||||
c1f6e23b161735085a00a36cb72926bbe187a63eabcb4425a4f5acad5fe3ff4a /tmp/repo22-manual-plain/src/README.txt
|
||||
20bf72d8cbc4dbc7e214b671f2547e3e8d57d455d10ac6b645656ef26afc6880 /tmp/repo22-manual-plain/src/plain.bin
|
||||
fec83f76b8b7eb1d83a9a51246e093c319061838b04633184e9a0c601a386ff3 /tmp/repo22-manual-plain/test.erofs
|
||||
fec83f76b8b7eb1d83a9a51246e093c319061838b04633184e9a0c601a386ff3 /tmp/repo22-manual-plain/test-repeat.erofs
|
||||
Filesystem magic number: 0xE0F5E1E2
|
||||
Filesystem blocksize: 4096
|
||||
Filesystem blocks: 2560
|
||||
Filesystem inode count: 3
|
||||
Filesystem created: Thu Jan 1 00:00:00 1970
|
||||
Filesystem features: sb_csum mtime
|
||||
Filesystem UUID: 00000000-0000-0000-0000-000000000022
|
||||
```
|
||||
|
||||
镜像大小为 10485760 字节,即 10 MiB。两次独立输出逐字节相同。
|
||||
|
||||
复制并验证:
|
||||
|
||||
```sh
|
||||
setsid -w scp -O <同上 SSH 认证选项> -P 9222 \
|
||||
/tmp/repo22-manual-plain/test.erofs [email protected]:/root/test.erofs
|
||||
ls -l /root/test.erofs
|
||||
sha256 /root/test.erofs
|
||||
```
|
||||
|
||||
```text
|
||||
-rw-r--r-- 1 root wheel 10485760 Aug 8 11:42 /root/test.erofs
|
||||
SHA256 (/root/test.erofs) = fec83f76b8b7eb1d83a9a51246e093c319061838b04633184e9a0c601a386ff3
|
||||
```
|
||||
|
||||
## 6. TC001-mount-basic
|
||||
|
||||
原文步骤前仅做规定前置条件:把 canonical 模块放入标准搜索目录,并附加镜像为 `/dev/md0`。
|
||||
|
||||
```sh
|
||||
ls -l /boot/modules/erofs.ko
|
||||
cp -f /root/erofs.ko /boot/modules/erofs.ko
|
||||
sha256 /boot/modules/erofs.ko
|
||||
umount /mnt/test 2>/dev/null
|
||||
mdconfig -d -u md0 2>/dev/null
|
||||
mdconfig -a -t vnode -f /root/test.erofs -u 0
|
||||
echo "mdconfig_attach_exit=$?"
|
||||
mdconfig -lv
|
||||
```
|
||||
|
||||
```text
|
||||
ls: /boot/modules/erofs.ko: No such file or directory
|
||||
SHA256 (/boot/modules/erofs.ko) = 6c13819ee36b1f7fe62ea084640fdefb2e871f245462291ce4c18f1ee93a684f
|
||||
mdconfig_attach_exit=0
|
||||
md0 vnode 10M /root/test.erofs -
|
||||
```
|
||||
|
||||
原文步骤 1-5:
|
||||
|
||||
```sh
|
||||
kldload erofs; echo "tc001_step1_exit=$?"
|
||||
kldstat | grep erofs; echo "tc001_step2_pipeline_exit=$?"
|
||||
mkdir -p /mnt/test; echo "tc001_step3_exit=$?"
|
||||
mount -t erofs /dev/md0 /mnt/test; echo "tc001_step4_exit=$?"
|
||||
mount | grep erofs; echo "tc001_step5a_pipeline_exit=$?"
|
||||
df -h /mnt/test; echo "tc001_step5b_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
tc001_step1_exit=0
|
||||
5 1 0xffffffff82822000 5d30 erofs.ko
|
||||
tc001_step2_pipeline_exit=0
|
||||
tc001_step3_exit=0
|
||||
tc001_step4_exit=0
|
||||
/dev/md0 on /mnt/test (erofs, local, read-only)
|
||||
tc001_step5a_pipeline_exit=0
|
||||
Filesystem Size Used Avail Capacity Mounted on
|
||||
/dev/md0 10M 10M 0B 100% /mnt/test
|
||||
tc001_step5b_exit=0
|
||||
```
|
||||
|
||||
补充读取验证:
|
||||
|
||||
```sh
|
||||
ls -la /mnt/test
|
||||
sha256 /mnt/test/README.txt /mnt/test/plain.bin
|
||||
cat /mnt/test/README.txt
|
||||
echo "tc001_content_exit=$?"
|
||||
dmesg | tail -20
|
||||
```
|
||||
|
||||
```text
|
||||
ls: /mnt/test/.: Operation not supported
|
||||
ls: /mnt/test/README.txt: Operation not supported
|
||||
ls: /mnt/test/plain.bin: Operation not supported
|
||||
total 10248
|
||||
drwxr-xr-x 2 root wheel 70 Jan 1 1970 .
|
||||
drwxr-xr-x 3 root wheel 512 Aug 8 11:42 ..
|
||||
-rw-r--r-- 1 root wheel 33 Jan 1 1970 README.txt
|
||||
-rw-r--r-- 1 root wheel 10481664 Jan 1 1970 plain.bin
|
||||
SHA256 (/mnt/test/README.txt) = c1f6e23b161735085a00a36cb72926bbe187a63eabcb4425a4f5acad5fe3ff4a
|
||||
SHA256 (/mnt/test/plain.bin) = 20bf72d8cbc4dbc7e214b671f2547e3e8d57d455d10ac6b645656ef26afc6880
|
||||
repo22 FreeBSD EROFS manual test
|
||||
tc001_content_exit=0
|
||||
```
|
||||
|
||||
`ls -la` 在读取附加 stat/pathconf 信息时打印 `Operation not supported`,但仍列出目录;两个文件均完整可读且 SHA-256 与 host 一致。该补充现象不属于 TC001 规定判定项,单独保留为观察。`dmesg | tail -20` 没有本轮 mount 错误;末尾两条 `bcmp` 行是上一轮历史记录。
|
||||
|
||||
原文步骤 6-7:
|
||||
|
||||
```sh
|
||||
umount /mnt/test; echo "tc001_step6_exit=$?"
|
||||
mount | grep erofs; echo "tc001_step7_pipeline_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
tc001_step6_exit=0
|
||||
<mount | grep erofs 无输出>
|
||||
tc001_step7_pipeline_exit=1
|
||||
```
|
||||
|
||||
步骤 7 的 grep 返回 1 正是“没有 EROFS 挂载”的预期。
|
||||
|
||||
**TC001 最终判定:PASS。**
|
||||
|
||||
## 7. TC009-statfs-basic
|
||||
|
||||
原文步骤 1-2:
|
||||
|
||||
```sh
|
||||
mount -t erofs /dev/md0 /mnt/test; echo "tc009_step1_exit=$?"
|
||||
df -h /mnt/test; echo "tc009_step2a_exit=$?"
|
||||
df -i /mnt/test; echo "tc009_step2b_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
tc009_step1_exit=0
|
||||
Filesystem Size Used Avail Capacity Mounted on
|
||||
/dev/md0 10M 10M 0B 100% /mnt/test
|
||||
tc009_step2a_exit=0
|
||||
Filesystem 1K-blocks Used Avail Capacity iused ifree %iused Mounted on
|
||||
/dev/md0 10240 10240 0 100% 3 0 100% /mnt/test
|
||||
tc009_step2b_exit=0
|
||||
```
|
||||
|
||||
总容量 10 MiB、可用块 0、inode 总数 3、可用 inode 0。
|
||||
|
||||
严格执行原文步骤 3:
|
||||
|
||||
```sh
|
||||
stat -f /mnt/test
|
||||
echo "tc009_step3_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
/mnt/test
|
||||
tc009_step3_exit=0
|
||||
```
|
||||
|
||||
FreeBSD `stat(1)` 的 `-f` 表示“后一个参数是格式字符串”,不是 GNU `stat -f` 的“显示文件系统状态”。命令只回显 `/mnt/test`,没有达到用例要求的“Shows EROFS filesystem type”。
|
||||
|
||||
严格执行原文步骤 4:
|
||||
|
||||
```sh
|
||||
stat -f -f "%b %f %c %a %d %i %t %n" /mnt/test
|
||||
echo "tc009_step4_exit=$?"
|
||||
```
|
||||
|
||||
```text
|
||||
stat: %b %f %c %a %d %i %t %n: No such file or directory
|
||||
-f
|
||||
tc009_step4_exit=1
|
||||
```
|
||||
|
||||
原文步骤 5 与补充容量核对:
|
||||
|
||||
```sh
|
||||
mount | grep /mnt/test | grep read-only
|
||||
echo "tc009_step5_pipeline_exit=$?"
|
||||
mount | grep /mnt/test
|
||||
df -k /mnt/test
|
||||
```
|
||||
|
||||
```text
|
||||
/dev/md0 on /mnt/test (erofs, local, read-only)
|
||||
tc009_step5_pipeline_exit=0
|
||||
/dev/md0 on /mnt/test (erofs, local, read-only)
|
||||
Filesystem 1024-blocks Used Avail Capacity Mounted on
|
||||
/dev/md0 10240 10240 0 100% /mnt/test
|
||||
```
|
||||
|
||||
`10240` 个 1 KiB 块等于 `2560` 个 4096-byte 块;只读标志、文件系统类型和 free=0 均正确。但严格按用例判定,步骤 3 未产生要求的信息,步骤 4 退出 1。
|
||||
|
||||
**TC009 最终判定:FAIL。失败原因是测试文档中的 `stat` 命令与 FreeBSD `stat(1)` CLI 语义不兼容;本轮未修改测试文档或源码。**
|
||||
|
||||
## 8. 清理与最终状态
|
||||
|
||||
```sh
|
||||
umount /mnt/test; echo "cleanup_umount_exit=$?"
|
||||
mdconfig -d -u md0; echo "cleanup_mdconfig_exit=$?"
|
||||
kldunload erofs; echo "cleanup_kldunload_exit=$?"
|
||||
rmdir /mnt/test; echo "cleanup_rmdir_exit=$?"
|
||||
rm -f /boot/modules/erofs.ko /root/test.erofs /root/erofs.ko /root/repo22-erofs.ko
|
||||
echo "cleanup_rm_exit=$?"
|
||||
kldstat | grep -i erofs; echo "final_kldstat_grep_exit=$?"
|
||||
mount | grep -i erofs; echo "final_mount_grep_exit=$?"
|
||||
mdconfig -l
|
||||
```
|
||||
|
||||
```text
|
||||
cleanup_umount_exit=0
|
||||
cleanup_mdconfig_exit=0
|
||||
cleanup_kldunload_exit=0
|
||||
cleanup_rmdir_exit=0
|
||||
cleanup_rm_exit=0
|
||||
final_kldstat_grep_exit=1
|
||||
final_mount_grep_exit=1
|
||||
<mdconfig -l 无输出>
|
||||
```
|
||||
|
||||
结论:guest 清理完成,无模块、挂载、md 设备或本轮临时文件残留。
|
||||
|
||||
## 最终判定
|
||||
|
||||
- `bcmp` 修复后模块构建与 host 检查:**PASS**
|
||||
- guest `kldload` / `kldstat` / `kldunload`:**PASS**
|
||||
- 固定 10 MiB plain 镜像生成、重复性与 SHA-256:**PASS**
|
||||
- TC001-mount-basic:**PASS**
|
||||
- TC009-statfs-basic:**FAIL(测试命令不兼容 FreeBSD `stat(1)`)**
|
||||
- BLOCKED:**0**
|
||||
- 源码修改:**无**
|
||||
- wrapper/CI:**未生成**
|
||||
@@ -0,0 +1,141 @@
|
||||
# repo22 压缩功能手工测试报告
|
||||
|
||||
- 执行日期:2026-08-08(UTC)
|
||||
- 执行时段:约 12:58-13:07 UTC
|
||||
- 测试对象:`/work/repo-community/repo22`
|
||||
- 测试提交:`f230129565677e602510347a55a80856237fe909`
|
||||
- 远端基线:执行前 `xdm/main` 指向同一提交
|
||||
- FreeBSD VM:15.0-RELEASE-p8 amd64,QEMU TCG,SSH `127.0.0.1:9222`
|
||||
- 镜像工具:erofs-utils 1.8.6
|
||||
- 执行方式:逐条 host/guest shell 命令、SSH/SCP、`mdconfig` 和真实内核挂载;未实现 CI 或测试 wrapper
|
||||
- 总体结论:**压缩功能不可验收。真实 compact LZ4 镜像可挂载,但首个 4 KiB 数据读取返回 `EINTEGRITY`,输出 0 字节。**
|
||||
|
||||
## 状态定义
|
||||
|
||||
| 状态 | 含义 |
|
||||
|---|---|
|
||||
| PASS | 该步骤已经在本轮实际执行,结果符合预期 |
|
||||
| KERNEL-FAIL | 真实镜像进入 FreeBSD 内核路径后失败 |
|
||||
| MKFS-UNAVAILABLE | erofs-utils 1.8.6 不能直接生成测试要求的镜像 |
|
||||
| TEST-DOC | 当前 Markdown 的命令或预期不能可靠验证目标行为 |
|
||||
| NOT RUN | 已准备 fixture,但收到立即停止新增测试的指令后未进入对应内核路径 |
|
||||
|
||||
## 最先出现的内核失败
|
||||
|
||||
使用 erofs-utils 1.8.6 生成根目录单文件 compact LZ4 镜像:
|
||||
|
||||
```sh
|
||||
mkfs.erofs -T0 --all-root --ignore-mtime -x-1 -Uclear \
|
||||
-zlz4 -C4096 lz4-root-compact-4k.erofs src-root-lz4
|
||||
```
|
||||
|
||||
`dump.erofs` 确认该文件不是 plain fallback:
|
||||
|
||||
```text
|
||||
Filesystem features: sb_csum mtime 0padding
|
||||
Path : /test-lz4.txt
|
||||
Size: 1048576 On-disk size: 8192 regular file
|
||||
NID: 39 Layout: 3 Compression ratio: 0.78%
|
||||
Ext 0: logical 0..639004, physical 4096..8192, physical length 4096
|
||||
Ext 1: logical 639004..1048576, physical 8192..12288, physical length 4096
|
||||
```
|
||||
|
||||
Guest 中模块加载和挂载成功:
|
||||
|
||||
```text
|
||||
SHA256 (/root/erofs.ko) = c81957971e670c8b8cc45d5119f568eb84adb6114699538588e5a842ea4d97b3
|
||||
kldload_rc=0
|
||||
5 1 0xffffffff82822000 8468 erofs.ko
|
||||
/dev/md0 on /mnt/repo22-test (erofs, local, read-only)
|
||||
```
|
||||
|
||||
文件 lookup 和 `stat` 成功后,第一次读取即失败:
|
||||
|
||||
```text
|
||||
$ sha256 /mnt/repo22-test/test-lz4.txt
|
||||
sha256: /mnt/repo22-test/test-lz4.txt: Integrity check failed
|
||||
read_rc=1
|
||||
|
||||
$ dd if=/mnt/repo22-test/test-lz4.txt of=/tmp/repo22-first4k bs=4096 count=1
|
||||
dd: /mnt/repo22-test/test-lz4.txt: Integrity check failed
|
||||
0+0 records in
|
||||
0+0 records out
|
||||
0 bytes transferred
|
||||
first_read_rc=1
|
||||
```
|
||||
|
||||
因此首个确定性压缩内核失败是:
|
||||
|
||||
- fixture:真实 `mkfs.erofs 1.8.6` compact LZ4,Layout 3;
|
||||
- mount:PASS;
|
||||
- lookup/stat:PASS;
|
||||
- 首个 4 KiB read:**KERNEL-FAIL / `EINTEGRITY`**;
|
||||
- 用户缓冲区:0 字节,没有把部分输出误判为成功;
|
||||
- 稳定性:命令后 VM 仍响应,时间为 `2026-08-08T13:06:43Z`,未观察到 panic 或 hang。
|
||||
|
||||
并发静态审查已经指出 compact 索引位置按 filesystem block size 计算,而不是按 compact index pack 大小计算;本轮失败与该问题相符,但手工测试本身只证明“真实 compact LZ4 首读失败”,不单独宣称完成了代码级根因证明。
|
||||
|
||||
## 已尝试 TC 结果
|
||||
|
||||
| TC | 状态 | 本轮证据 |
|
||||
|---|---|---|
|
||||
| TC003-lz4-compressed-read | **KERNEL-FAIL** | 真实 compact LZ4 镜像 mount PASS;完整 hash 与首个 4 KiB read 都返回 `EINTEGRITY` |
|
||||
| TC084-lz4-basic | **KERNEL-FAIL** | 与 TC003 共用的最小真实 LZ4 基础读路径失败,无法进行透明解压和内容比较 |
|
||||
| TC086-lz4-sequential-read | **TEST-DOC** | 用例要求不存在的 `vfs:erofs::read` DTrace provider,并把 TCG VM 上固定吞吐量当功能判据 |
|
||||
| TC088-lz4-config-handling | **TEST-DOC** | 4K/64K/256K fixture 均生成;“pcluster 越大则镜像必然更小、随机读必然更慢”不是对任意语料都成立的功能断言 |
|
||||
| TC090-lz4-pcluster-64k | **TEST-DOC** | 步骤 4 把 `dd ... of=/tmp/out` 再 pipe 给 `tail/head`,pipe 中没有文件数据,无法验证跨边界读取 |
|
||||
| TC098-fragments-support | **TEST-DOC** | EROFS fragments 使用 packed inode,不是 Markdown 所写的独立 fragment device;`-o device=/dev/md1` 验证的是多设备而非 fragments |
|
||||
| TC101-unified-address-mapping | **TEST-DOC** | 前置要求多设备,但 mount 命令只提供 device0;`ktrace` 也不能直接证明驱动选中了正确 EROFS device id |
|
||||
| TC110-lzma-corrupt | **TEST-DOC** | 固定覆盖 image offset 8192 没有先定位目标文件的 compressed extent,可能破坏无关数据或元数据,不能确定性验证 LZMA 错误路径 |
|
||||
| TC115-unsupported-algorithm | **MKFS-UNAVAILABLE** | mkfs 1.8.6 只提供 lz4/lz4hc/lzma/deflate/libdeflate/zstd,不能直接生成 future algorithm ID;需要有校验意识的定向 ABI patch fixture |
|
||||
| TC116-truncated-compressed | **TEST-DOC** | 盲目从镜像末尾截去 4096 字节不保证命中目标 compressed extent;可能只移除 padding、其他文件或元数据 |
|
||||
|
||||
## 已生成但按停止指令未运行的 fixture
|
||||
|
||||
以下 fixture 的 `mkfs.erofs` 和目标布局检查已经成功,但没有据此把对应 TC 标为 PASS:
|
||||
|
||||
| TC 范围 | Fixture 准备结果 | TC 状态 |
|
||||
|---|---|---|
|
||||
| TC004、TC108、TC109 | 101 MiB MicroLZMA/LZMA level 6;Layout 3;19 个真实 compressed extents | NOT RUN |
|
||||
| TC085、TC087、TC089 | 256 MiB/50 MiB/4K LZ4 corpus;compact 4K 与 legacy full 镜像均生成 | NOT RUN |
|
||||
| TC090 | compact 64K big-pcluster 镜像生成,superblock 含 `compr_cfgs big_pcluster` | TEST-DOC,未运行内核读 |
|
||||
| TC091、TC092 | `-Eztailpacking` 镜像生成,superblock 含 `ztailpacking` | NOT RUN |
|
||||
| TC093、TC095 | single-device 1 MiB chunk 与 512 KiB chunk-index 镜像生成,Layout 4 | NOT RUN |
|
||||
| TC094、TC096、TC099 | main image + 5 MiB external blob 生成;首次必须预创建 blob 文件后 mkfs 才成功 | NOT RUN |
|
||||
| TC097、TC100 | 没有生成确定性 invalid table 或四外部设备 fixture | NOT RUN |
|
||||
| TC102-TC104 | DEFLATE level 1/6/9 镜像均生成,file1m 为真实 Layout 3 | NOT RUN |
|
||||
| TC105-TC107 | ZSTD level 1/15/22 镜像均生成,file1m 为真实 Layout 3 | NOT RUN |
|
||||
|
||||
收到“立即停止新增测试”指令后,没有继续 mount/read 上述镜像,也没有继续制作 corruption、unsupported algorithm、interlaced、extent 或 partial-ref 变体。
|
||||
|
||||
## Fixture 审核摘要
|
||||
|
||||
本轮生成并由 `dump.erofs` 确认的主要布局:
|
||||
|
||||
| 镜像 | 关键特征 |
|
||||
|---|---|
|
||||
| `lz4-compact-4k.erofs` | Layout 3 compact;`sb_csum mtime 0padding` |
|
||||
| `lz4-full-4k.erofs` | Layout 1 full;`-Elegacy-compress` |
|
||||
| `lz4-compact-64k.erofs` | Layout 3;`compr_cfgs big_pcluster`;真实物理 extent 最大 53248 字节 |
|
||||
| `lz4-compact-256k.erofs` | Layout 3;`compr_cfgs big_pcluster` |
|
||||
| `lz4-ztailpacking.erofs` | `ztailpacking` incompat feature |
|
||||
| `lz4-fragments.erofs` | `fragments dedupe`,目标文件物理长度由 packed inode 提供 |
|
||||
| `lzma-level6.erofs` | 101 MiB 逻辑文件,126976 字节 on-disk,Layout 3 |
|
||||
| `deflate-level{1,6,9}.erofs` | 三个等级均为真实 compressed Layout 3 |
|
||||
| `zstd-level{1,15,22}.erofs` | 三个等级均为真实 compressed Layout 3 |
|
||||
| `chunk-single-1m.erofs` | `chunked_file`,Layout 4,三个 1 MiB extent |
|
||||
| `chunk-index-512k.erofs` | `chunked_file`,Layout 4,8-byte chunk index fixture |
|
||||
| `chunk-multidev-main.erofs` + `.blob` | 4 KiB metadata image + 5 MiB external blob |
|
||||
|
||||
所有镜像、source corpus、VM overlay 和模块 build 产物仅位于 `/work/build`、guest `/root` 或 repo22 忽略的 `build/`,没有加入 Git。
|
||||
|
||||
## 其他观察
|
||||
|
||||
1. 多文件嵌套目录镜像首次直接 lookup `/files/test-lz4.txt` 偶发返回 `ENOENT`;先执行目录枚举后 `stat` 可成功。根目录单文件 fixture 消除了该前置干扰,并稳定进入压缩 read 后返回 `EINTEGRITY`。
|
||||
2. FreeBSD `ls -l` 对 EROFS 条目打印 `Operation not supported`,同时仍能列出项目;这看起来来自 ACL/扩展属性查询,不应当被误记为目录读取失败。
|
||||
3. dmesg 中的 `bcmp undefined` 是前一轮旧模块的历史日志;本轮 canonical `/root/erofs.ko` 的 `kldload` 返回 0,且模块 SHA-256 为新的 `c819...97b3`。
|
||||
4. 并发静态审查另报 incompat `0x8`、64K `clusterofs` 截断、通用剥前导零和短输出泄漏。本轮遵照停止指令没有再构造独立运行时 fixture 复核这些问题。
|
||||
|
||||
## 结论
|
||||
|
||||
`f2301295` 的压缩重构已经达到“模块可加载、真实压缩镜像可挂载”的阶段,但尚未达到“可读取最基本 compact LZ4 文件”的最低 feature gate。应先修复 compact zmap/read 闭环,并针对静态审查列出的五个确定性阻断补足定向手测,再恢复其余算法和高级布局验证。
|
||||
@@ -0,0 +1,84 @@
|
||||
# repo22 compact LZ4 root-cause verification
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source parent: `c939c472134c69fa4665f52ab1aec9a17cd583b9`
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Image tool: erofs-utils 1.8.6
|
||||
- Scope: the existing minimal compact LZ4 fixture only; no CI or wrapper was added
|
||||
|
||||
## Changes under verification
|
||||
|
||||
1. Accept incompat bit `0x8` for compressed HEAD2 while rejecting images with
|
||||
external device slots until true multi-device I/O exists.
|
||||
2. Widen zmap `clusterofs` so the 64 KiB NONHEAD sentinel is representable.
|
||||
3. Count compact indexes using the logical cluster size.
|
||||
4. Apply leading zero padding according to the algorithm and the LZ4
|
||||
`0padding` feature instead of stripping it unconditionally for all streams.
|
||||
5. Zero decompression targets and require exact output lengths.
|
||||
6. Apply DEFLATE `windowbits` and ZSTD `windowlog` to the decoder calls.
|
||||
7. Separate LZ4 full and partial completion rules and validate input/output
|
||||
boundaries.
|
||||
8. Permit valid compact NONHEAD deltas to cross compact-pack boundaries. The
|
||||
previous BSD-only restriction caused the first real image read to fail at
|
||||
the second compressed extent head.
|
||||
|
||||
## Build and module lifecycle
|
||||
|
||||
`./build.sh` completed successfully. The resulting module had no unresolved
|
||||
`bcmp` symbol and contained no temporary zmap/zdata diagnostic strings.
|
||||
|
||||
On the guest, all lifecycle operations succeeded:
|
||||
|
||||
```text
|
||||
kldload_rc=0
|
||||
mount_rc=0
|
||||
umount_rc=0
|
||||
md_detach_rc=0
|
||||
kldunload_rc=0
|
||||
```
|
||||
|
||||
## Real image result
|
||||
|
||||
The fixture was generated by the existing manual-test command:
|
||||
|
||||
```sh
|
||||
mkfs.erofs -T0 --all-root --ignore-mtime -x-1 -Uclear \
|
||||
-zlz4 -C4096 lz4-root-compact-4k.erofs src-root-lz4
|
||||
```
|
||||
|
||||
`dump.erofs` identifies `/test-lz4.txt` as layout 3 (compact), 1 MiB logical,
|
||||
8 KiB on disk, with two compressed extents. The same image previously failed
|
||||
its first read with `EINTEGRITY` on `f2301295`.
|
||||
|
||||
Final FreeBSD results:
|
||||
|
||||
```text
|
||||
SHA256 (/mnt/repo22-rootfix/test-lz4.txt) = 370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
SHA256 (source test-lz4.txt) = 370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
SHA256 (first 4096 bytes from EROFS) = 3d6d283a95f80b3e4a9c243cf82dc0644283910b6a3be931fede5d771e132b14
|
||||
SHA256 (first 4096 bytes from source) = 3d6d283a95f80b3e4a9c243cf82dc0644283910b6a3be931fede5d771e132b14
|
||||
full_read_rc=0
|
||||
first_4k_read_rc=0
|
||||
```
|
||||
|
||||
Result: the minimal real compact LZ4 mount/read gate passes.
|
||||
|
||||
## Integration follow-up
|
||||
|
||||
The integration review after `6c9543892` found that the leading-padding
|
||||
predicate still selected every non-LZ4 algorithm. The predicate was narrowed
|
||||
to LZ4 streams with the `0padding` incompat feature, matching the stated
|
||||
algorithm-specific behavior. `./build.sh` and the same FreeBSD 15 compact LZ4
|
||||
mount/read lifecycle were rerun after this correction; the full-file SHA256
|
||||
remained `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52`,
|
||||
the first 4096-byte read returned 4096 bytes, and module unload succeeded.
|
||||
|
||||
## Remaining gaps
|
||||
|
||||
This batch does not claim runtime completion for legacy full indexes, 64 KiB
|
||||
or larger pclusters, ztailpacking, interlaced pclusters, fragments, extents,
|
||||
partial references, MicroLZMA, DEFLATE, or ZSTD images. DEFLATE and ZSTD
|
||||
configuration handling was corrected and the module loaded on this FreeBSD 15
|
||||
kernel, but those algorithms were not image-tested in this converged batch.
|
||||
The LZ4 partial-decoding branch was code-reviewed but is not exercised by the
|
||||
current synchronous full-extent read path.
|
||||
@@ -0,0 +1,88 @@
|
||||
# repo22 non-LZ4 leading-zero verification
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source baseline: `7cba92c0ec434b96180200b09e5dddfa213e67f5`
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Image tool: erofs-utils 1.8.6
|
||||
- Scope: MicroLZMA, DEFLATE, and ZSTD leading-zero handling only
|
||||
|
||||
## Code change
|
||||
|
||||
Linux 7.1-rc1 calls `z_erofs_fixup_insize()` for every MicroLZMA, DEFLATE,
|
||||
and ZSTD stream, while LZ4 only requires it when the `LZ4_0PADDING` feature is
|
||||
enabled. The FreeBSD dispatcher now applies the same predicate. Existing
|
||||
first-block limits, empty-input rejection, and input-length subtraction remain
|
||||
unchanged.
|
||||
|
||||
## Build and module lifecycle
|
||||
|
||||
`./build.sh` completed successfully and produced `build/erofs.ko` with SHA256:
|
||||
|
||||
```text
|
||||
4b3459a02da29642cb8e61f58e34ff5266e668e86256937c91b8af7e0e31fdcb
|
||||
```
|
||||
|
||||
FreeBSD 15 results:
|
||||
|
||||
```text
|
||||
lifecycle_kldload_rc=0
|
||||
lifecycle_kldunload_rc=0
|
||||
dmesg_delta_lines=0
|
||||
dmesg_error_grep_rc=1
|
||||
```
|
||||
|
||||
The last two lines mean the isolated lifecycle and read pass added no kernel
|
||||
messages, so no decompression, linker, panic, or integrity error was present.
|
||||
|
||||
## Deterministic images
|
||||
|
||||
The source file was 1 MiB and had SHA256:
|
||||
|
||||
```text
|
||||
370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
```
|
||||
|
||||
The images were regenerated with erofs-utils 1.8.6:
|
||||
|
||||
```sh
|
||||
mkfs.erofs -T0 --all-root --ignore-mtime -x-1 -Uclear \
|
||||
-zlzma,level=6 -C4096 lzma-root.erofs src-root
|
||||
mkfs.erofs -T0 --all-root --ignore-mtime -x-1 -Uclear \
|
||||
-zdeflate,level=6 -C4096 deflate-root.erofs src-root
|
||||
mkfs.erofs -T0 --all-root --ignore-mtime -x-1 -Uclear \
|
||||
-zzstd,level=3 -C4096 zstd-root.erofs src-root
|
||||
```
|
||||
|
||||
`dump.erofs` reported layout 3 for `/test-lz4.txt` in all images. MicroLZMA
|
||||
and ZSTD used one 4096-byte physical extent; DEFLATE used two 4096-byte
|
||||
physical extents.
|
||||
|
||||
## FreeBSD read results
|
||||
|
||||
Each image was attached with `mdconfig`, mounted read-only, read completely by
|
||||
`sha256`, unmounted, and detached before testing the next image.
|
||||
|
||||
```text
|
||||
lzma mount_rc=0 read_rc=0 sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
lzma umount_rc=0 detach_rc=0
|
||||
deflate mount_rc=0 read_rc=0 sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
deflate umount_rc=0 detach_rc=0
|
||||
zstd mount_rc=0 read_rc=0 sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
zstd umount_rc=0 detach_rc=0
|
||||
```
|
||||
|
||||
Result: MicroLZMA, DEFLATE, and ZSTD all pass real-image full-file integrity
|
||||
verification on this FreeBSD 15 kernel configuration.
|
||||
|
||||
## Notes and limits
|
||||
|
||||
An exploratory image with the file below a `/files` directory mounted, but the
|
||||
target lookup failed. Root-level fixtures were used to isolate decompression
|
||||
from the separately tracked directory lookup work. This does not change the
|
||||
three successful compressed read results above.
|
||||
|
||||
This batch does not claim coverage for other compression levels, large files,
|
||||
random access, corrupt streams, alternative FreeBSD kernel configurations, or
|
||||
other compression mapping features. DEFLATE and ZSTD kernel symbols were
|
||||
available on the tested FreeBSD 15 GENERIC kernel; no unsupported algorithm or
|
||||
kernel-symbol limitation was encountered.
|
||||
@@ -0,0 +1,99 @@
|
||||
# repo22 compressed mapping shape manual test report
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source baseline: `b1f9e7c0ef5ffb9c4aab38e4047534ad2071af6c`
|
||||
- Remote baseline before testing: `xdm/main` at the same commit
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Image tool: erofs-utils 1.8.6
|
||||
- Module SHA256: `4b3459a02da29642cb8e61f58e34ff5266e668e86256937c91b8af7e0e31fdcb`
|
||||
- Scope: manual image generation, layout inspection, and the completed compact-index kernel reads; no CI or test wrapper was added
|
||||
|
||||
## Status definitions
|
||||
|
||||
| Status | Meaning |
|
||||
|---|---|
|
||||
| PASS | The stated mkfs/layout or kernel operation was actually run and matched its deterministic expectation |
|
||||
| KERNEL-FAIL | A real image reached the FreeBSD kernel path and failed |
|
||||
| MKFS-UNAVAILABLE | erofs-utils 1.8.6 cannot generate the required format directly |
|
||||
| TEST-DOC | The existing Markdown command or assertion does not deterministically test the stated feature |
|
||||
| NOT RUN | The fixture was generated, but kernel execution stopped on user request and no feature pass is claimed |
|
||||
|
||||
## Generated fixture matrix
|
||||
|
||||
All images were regenerated under `/work/build/repo22-shape-manual-20260808T1407Z` with fixed timestamps, root ownership, disabled xattrs, and a cleared UUID. Images, sources, VM state, and module build products were not staged.
|
||||
|
||||
| Shape | mkfs/layout status | Evidence | Kernel status |
|
||||
|---|---|---|---|
|
||||
| Compact indexes | PASS | default `-zlz4 -C4096`; `/shape.dat` is Layout 3 with two compressed extents | PASS |
|
||||
| Legacy full indexes | PASS | `-Elegacy-compress`; `/shape.dat` is Layout 1 with two compressed extents | NOT RUN |
|
||||
| 64 KiB big pcluster | PASS | `-C65536`; superblock reports `compr_cfgs big_pcluster`; Layout 3 has one 1 MiB logical extent backed by 8192 bytes | NOT RUN |
|
||||
| Inline ztailpacking | PASS | `-Eztailpacking`; superblock reports `ztailpacking`; `/inline.dat` has on-disk size 0 and physical bytes `1352..1787` inside the metadata block | NOT RUN |
|
||||
| Fragments / packed inode | PASS | `-Eall-fragments`; superblock reports packed NID 42 plus `fragments dedupe`; `/fragment.dat` has on-disk size 0 | NOT RUN |
|
||||
| Partial reference | PASS | `-Ededupe`; mkfs reports `Dedupe 409572 compressed data (delta 315374)` and `/b.dat` reuses the physical extent at `8192..12288` | NOT RUN |
|
||||
| Extent metadata format | MKFS-UNAVAILABLE | erofs-utils v1.8.6 has no extent-map advise/structure or mkfs option for the newer extent metadata format; `--max-extent-bytes` only limits decompressed extent size | NOT RUN |
|
||||
|
||||
Host-side `fsck.erofs --extract` succeeded for the initially generated compact, full, big-pcluster, ztailpacking-option, and all-fragments images. This is fixture validation only, not a FreeBSD kernel feature pass.
|
||||
|
||||
## Completed FreeBSD compact-index verification
|
||||
|
||||
The compact image used a deterministic 1 MiB file. `dump.erofs` reported:
|
||||
|
||||
```text
|
||||
Layout: 3
|
||||
Ext 0: logical 0..639004, physical 4096..8192, physical length 4096
|
||||
Ext 1: logical 639004..1048576, physical 8192..12288, physical length 4096
|
||||
```
|
||||
|
||||
Guest operations completed as follows:
|
||||
|
||||
```text
|
||||
kldload_rc=0
|
||||
md_attach_rc=0
|
||||
mount_rc=0
|
||||
stat_size=1048576
|
||||
full_sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
expected_full_sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
cross_extent_offset=638972
|
||||
cross_extent_length=128
|
||||
cross_extent_sha256=941b6e3cb9a7384428d93ca248eb9630782538e04caba30dbfbe31baacca8f60
|
||||
expected_cross_extent_sha256=941b6e3cb9a7384428d93ca248eb9630782538e04caba30dbfbe31baacca8f60
|
||||
```
|
||||
|
||||
Result: compact full-file integrity and a read crossing the logical boundary immediately before the second compressed extent both pass.
|
||||
|
||||
## First failure and dmesg
|
||||
|
||||
No KERNEL-FAIL occurred in this batch. The first incomplete command was:
|
||||
|
||||
```sh
|
||||
dd if=/mnt/repo22-shape/shape.dat bs=1 skip=777777 count=8192 | sha256 -q
|
||||
```
|
||||
|
||||
It was operator-terminated when the user requested immediate convergence. Byte-sized `dd` caused repeated page reads under QEMU TCG and was an inefficient manual command; it did not return a kernel error and is not classified as KERNEL-FAIL.
|
||||
|
||||
The guest was responsive after termination, and the mount and md device were cleaned up. The dmesg tail contained only successful mapping diagnostics such as:
|
||||
|
||||
```text
|
||||
erofs zread: nid=39 la=1044480 mapla=639004 llen=409572 pa=8192 plen=4096 flags=1 alg=0
|
||||
erofs zread: nid=39 la=0 mapla=0 llen=639004 pa=4096 plen=4096 flags=1 alg=0
|
||||
```
|
||||
|
||||
There was no new `EINTEGRITY`, decompression error, panic, trap, or hang message. The volume of unconditional `erofs zread` diagnostics should be reviewed separately as a style/logging issue.
|
||||
|
||||
## TC disposition
|
||||
|
||||
| TC area | Status | Result |
|
||||
|---|---|---|
|
||||
| TC084 compact LZ4 basic | PASS | Complete SHA256 and cross-extent boundary read match the source |
|
||||
| TC088 pcluster configuration | TEST-DOC | Previous monotonic ratio/performance assertions were invalid; commands and pass criteria were corrected |
|
||||
| TC090 64 KiB pcluster | TEST-DOC | Previous pipeline read from files redirected away from the pipe; deterministic cross-boundary command was corrected; kernel execution remains NOT RUN |
|
||||
| TC091 ztailpacking | TEST-DOC | Enabling the option alone did not tail-pack the first 1 MiB target; the TC now requires `dump.erofs` proof of an inline extent; kernel execution remains NOT RUN |
|
||||
| TC098 fragments | TEST-DOC | Previous TC incorrectly described fragments as an external device; it now tests the same-image packed inode; kernel execution remains NOT RUN |
|
||||
| TC115 unsupported algorithm | MKFS-UNAVAILABLE | mkfs.erofs 1.8.6 only emits its supported algorithm IDs; a checksum-aware ABI patch fixture is required |
|
||||
| TC116 truncated compressed data | TEST-DOC | Blind `truncate -s -4096` was nondeterministic; the TC now truncates inside a located target extent; kernel execution remains NOT RUN |
|
||||
|
||||
TC085-TC087, TC089, TC092-TC101 other than TC098 were read for dependencies but were not executed in this converged batch. Multi-device and chunk tests are independent follow-up work and are not implied by the packed-inode fragment fixture.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The completed kernel evidence establishes compact-index LZ4 full-read and compressed-extent-boundary correctness on the tested FreeBSD 15 guest. Legacy full indexes, big pclusters, inline ztailpacking, fragments, and partial references are confirmed as real erofs-utils 1.8.6 fixtures but remain explicitly NOT RUN in the FreeBSD kernel after the stop instruction. No kernel failure was observed.
|
||||
@@ -0,0 +1,53 @@
|
||||
# repo22 remaining compressed mapping manual read report
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source baseline: `df5b92c902295dc74a08aef89fd93d698bbddf21`
|
||||
- Remote baseline before testing: `xdm/main` at the same commit
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Image tool: erofs-utils 1.8.6
|
||||
- Scope: kernel mount/read verification for the five previously generated compression layouts only; no source, CI, or test-wrapper changes
|
||||
|
||||
## Result matrix
|
||||
|
||||
| Mode | Fixture proof | Mount | Full SHA256 | Offset/boundary read | Isolated dmesg | Result |
|
||||
|---|---|---:|---|---|---|---|
|
||||
| Legacy full index | Layout 1, two compressed extents | PASS | `EIO`, no digest | `EIO` at offset 638972 across the 639004 extent boundary | No panic, trap, integrity, corruption, or decompression-error signature; guest responsive | **KERNEL-FAIL** |
|
||||
| 64 KiB pcluster | Layout 3; `compr_cfgs big_pcluster`; one 1 MiB logical extent backed by 8192 bytes | PASS | Match | Match at offset 65504, length 64 | No error signature; guest responsive | **PASS** |
|
||||
| Inline ztailpacking | `ztailpacking`; compressed extent at physical 1352..1787 inside metadata block | PASS | Match | Match at offset 31744, length 2048 | No error signature; guest responsive | **PASS** |
|
||||
| Fragments / packed inode | `fragments`; packed NID; whole-file extent has zero direct on-disk bytes | PASS | Match | Match at offset 65536, length 8192 | No error signature; guest responsive | **PASS** |
|
||||
| Partial reference | `/b.dat` Layout 1 reuses `/a.dat` physical extent 8192..12288 at logical 641052..1050624 | PASS | `/b.dat` returns `EIO` | `EIO` at offset 641020 crossing into the reused extent; companion Layout 3 `/a.dat` matches | No error signature; guest responsive | **KERNEL-FAIL** |
|
||||
|
||||
All five fixtures contain the requested layout. No mode is classified as `FIXTURE-NOT-PROVEN` or `NOT RUN`.
|
||||
|
||||
## Integrity evidence
|
||||
|
||||
| Target | Full SHA256 |
|
||||
|---|---|
|
||||
| Legacy full reference | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` |
|
||||
| 64 KiB pcluster, actual and expected | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` |
|
||||
| Inline ztailpacking, actual and expected | `e2aa4a0a0cbcf422f397c7069a38ae0f073781386958e7db0dfa3ff2ca075513` |
|
||||
| Packed fragment, actual and expected | `a3a83e5c524b5ed446a06ce78cf407192a0c80119f15d2bc3489a50515eb49e0` |
|
||||
| Partial-ref `/b.dat` reference | `61b17076c2dfae88da7912d00df534b894cf6d27178863c6d8e91f8617ebb91e` |
|
||||
| Partial-ref companion `/a.dat`, actual and expected | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` |
|
||||
|
||||
Offset-read evidence:
|
||||
|
||||
| Mode | Actual | Expected |
|
||||
|---|---|---|
|
||||
| Legacy full | `f387a3a74488528803454f0c05601fb632d919f1bc5281538f687a3a6231ebea` from partial failed output | `941b6e3cb9a7384428d93ca248eb9630782538e04caba30dbfbe31baacca8f60` |
|
||||
| 64 KiB pcluster | `b6ebba880cfcc438044f943370b9936a130ce1cecf13d784a1eb871f0a126182` | same |
|
||||
| Inline ztailpacking | `3a630b7e9618c7e27efb1483058baaa0475ca325c3ecafb42778871d700afa96` | same |
|
||||
| Packed fragment | `a826cc36ad8c9eaa1606713117fcf118b7bae5fee7503b5619dbf024213059db` | same |
|
||||
| Partial reference | empty output `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | `941b6e3cb9a7384428d93ca248eb9630782538e04caba30dbfbe31baacca8f60` |
|
||||
|
||||
## Failure disposition
|
||||
|
||||
The legacy full-index image mounts, but both the complete read and a read crossing its compressed-extent boundary fail with `EIO`. The fixture and its source remain under `/work/build/repo22-shape-manual-20260808T1407Z`; copies plus per-mode dmesg files remain in the guest under `/root/repo22-short-20260808`.
|
||||
|
||||
The partial-reference fixture is proven: `/b.dat` reuses the final physical compressed extent of `/a.dat`. However, `/b.dat` itself uses Layout 1 and fails before partial-reference semantics can be accepted. The same image's Layout 3 `/a.dat` reads correctly. This result is therefore a kernel failure with the legacy full-index path as the immediate blocker, not a fixture failure.
|
||||
|
||||
Each mode used a separate md attachment and mount lifecycle. Cleanup succeeded after every run, the module remained loaded, and the guest answered a responsiveness check after every failure and pass. The isolated dmesg scans contained no panic, trap, explicit integrity, corruption, I/O-error, or decompression-error message; the two read failures surfaced only as user-visible `EIO`.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The 64 KiB big-pcluster, inline ztailpacking, and packed-inode fragment read paths pass the requested kernel verification. Legacy full indexes remain broken. The proven partial-reference fixture also fails because its target file traverses the broken Layout 1 path, so partial-reference support cannot be accepted until that blocker is fixed and the same fixture is rerun.
|
||||
@@ -0,0 +1,41 @@
|
||||
# repo22 legacy full-index fix manual report
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source baseline: `377d467a652beb2124d4adc0d1f8e735fca22344`
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Module SHA256: `f3b9fa70bfb8e98e2cef436583fd13cb91787514a5be76f9f108698d7586ca3c`
|
||||
- Scope: legacy full-index and partial-reference minimum regression only
|
||||
|
||||
## Root cause and fix
|
||||
|
||||
The proven legacy Layout 1 fixture maps its second extent as `HEAD1` with
|
||||
`clusterofs=28`, `pblk=2`, `m_la=639004`, `m_llen=409572`, and
|
||||
`m_plen=4096`. The LZ4 stream completes after consuming 2627 bytes; the
|
||||
remaining 1469 bytes of the pcluster are zero padding. The repo22 decoder
|
||||
incorrectly required all 4096 input bytes to be consumed and returned `EIO`.
|
||||
|
||||
The decoder now accepts complete output only when the remaining pcluster bytes
|
||||
are all zero, while continuing to reject nonzero trailing data. The mapping
|
||||
recorder also no longer clears `compressedblks` and `partialref` on every
|
||||
lcluster load, matching the Linux state machine and preserving lookback state.
|
||||
|
||||
## Results
|
||||
|
||||
| Test | Mount | Full SHA256 | Boundary read | Result |
|
||||
|---|---:|---|---|---|
|
||||
| Legacy full index `/shape.dat` | PASS | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` matches | offset 638972, 128 bytes: `941b6e3cb9a7384428d93ca248eb9630782538e04caba30dbfbe31baacca8f60` matches | **PASS** |
|
||||
| Partial reference `/b.dat` | PASS | read returns `EIO` | offset 641020 read returns no data | **KERNEL-FAIL** |
|
||||
|
||||
`./build.sh`, `kldload`, and exact-name `kldunload` passed. The isolated dmesg
|
||||
delta was empty, with no panic, trap, or integrity diagnostic.
|
||||
|
||||
## Remaining partial-reference hypothesis
|
||||
|
||||
The minimal reproducer is the existing
|
||||
`lz4-partial-ref.erofs` fixture: mount it and read `/b.dat`; the companion
|
||||
compact `/a.dat` remains the known-good source extent. The full-index mapping
|
||||
blocker is removed, but `zdata.c` still invokes `z_erofs_decompress()` with
|
||||
`partial=false` for every mapped extent even when `EROFS_MAP_PARTIAL_REF` is
|
||||
set. Linux propagates this state to partial LZ4 decoding. Per the convergence
|
||||
instruction, this batch records that hypothesis without further debugging or
|
||||
additional source changes.
|
||||
@@ -0,0 +1,33 @@
|
||||
# repo22 partial-reference fix manual report
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source baseline: `616d23e59b2bc16d9735ca5ad07502a21153bc0a`
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Module SHA256: `0270671fd73c4ee604294a5f94863f1fc8cc9a63083a4c83f89887891f06f1b2`
|
||||
- Scope: partial-reference output range and LZ4 regression only
|
||||
|
||||
## Root cause and fix
|
||||
|
||||
`zmap.c` correctly marked the reused extent with `EROFS_MAP_PARTIAL_REF`, but
|
||||
`zdata.c` always decompressed the complete mapped logical length and passed
|
||||
`partial=false`. Linux keeps such pclusters in partial-decoding mode and sets
|
||||
the decompressor output size to the highest byte needed by the current read.
|
||||
|
||||
The synchronous FreeBSD path now preserves that behavior. For a partial
|
||||
reference, it decodes only through `mapoff + want`, passes `partial=true`, and
|
||||
copies exactly the requested `want` bytes beginning at `mapoff`. Non-partial
|
||||
extents still require full logical output and retain strict LZ4 trailing-data
|
||||
validation.
|
||||
|
||||
## Results
|
||||
|
||||
| Test | Mount | Full SHA256 | Boundary read | Result |
|
||||
|---|---:|---|---|---|
|
||||
| Partial reference `/b.dat` | PASS | `61b17076c2dfae88da7912d00df534b894cf6d27178863c6d8e91f8617ebb91e` matches | offset 641020, 128 bytes: `941b6e3cb9a7384428d93ca248eb9630782538e04caba30dbfbe31baacca8f60` matches | **PASS** |
|
||||
| Legacy full index `/shape.dat` | PASS | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` matches | smoke only | **PASS** |
|
||||
| Compact index `/shape.dat` | PASS | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` matches | smoke only | **PASS** |
|
||||
|
||||
`./build.sh`, `kldload`, and exact-name `kldunload` passed. The isolated dmesg
|
||||
delta was empty, with no panic, trap, integrity diagnostic, or decompression
|
||||
error. Build objects, kernel module output, images, and VM overlays were not
|
||||
staged for commit.
|
||||
@@ -0,0 +1,46 @@
|
||||
# repo22 ACL/xattr integration smoke report
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Source baseline before this batch: `8e629bb9f798f0c76497bd2b623329a0aff430b9`
|
||||
- FreeBSD guest: 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Module SHA256: `18967bd32e89b08a2363241ed4b9b67344b7bfb555f1f77daa64f382925e4fda`
|
||||
- Scope: ACL/xattr worktree integration, build/link validation, and minimal
|
||||
Markdown-directed manual smoke tests
|
||||
|
||||
## Results
|
||||
|
||||
| Check | Result | Evidence |
|
||||
|---|---|---|
|
||||
| `./build.sh` | PASS | `build/erofs.ko` generated without errors |
|
||||
| unresolved `bcmp` | PASS | `nm -u build/erofs.ko` contains no `bcmp` |
|
||||
| ACL kernel dependency | PASS | module declares `acl_posix1e`; `kldload` succeeds |
|
||||
| module lifecycle | PASS | `kldload`, `kldstat`, and exact module-ID `kldunload` succeed |
|
||||
| TC005 user xattr smoke | PASS | `user.comment` lists and reads as `repo22-user-xattr` |
|
||||
| TC082 access ACL smoke | PASS | system xattr lists `posix_acl_access`; raw ACL reads successfully |
|
||||
| FreeBSD ACL decode | PASS | `getfacl` reports named user, group, mask, and other entries |
|
||||
| TC060 ACL capability | PASS | `getconf ACL_EXTENDED` returns `1` |
|
||||
| read-only access smoke | PASS | read succeeds and write access is denied |
|
||||
| cleanup | PASS | no EROFS module, mount, or md device remains |
|
||||
|
||||
The ACL fixture contained a real extended POSIX.1e access ACL with a named
|
||||
user and mask, so Linux did not collapse it into mode bits. `dump.erofs`
|
||||
reported 88 bytes of inode xattrs. FreeBSD returned:
|
||||
|
||||
```text
|
||||
user::rw-
|
||||
user:ntpd:r--
|
||||
group::r--
|
||||
mask::r--
|
||||
other::---
|
||||
```
|
||||
|
||||
The fixture image, source directory, kernel module, VM overlay, and generated
|
||||
objects remained under `/work/build` or `repo22/build` and were not staged.
|
||||
|
||||
## Remaining scope
|
||||
|
||||
- Default-directory ACL retrieval and access checks under multiple credentials
|
||||
were not exercised in this smoke batch.
|
||||
- Shared-xattr base calculation, long/packed prefixes, xattr filters, malformed
|
||||
xattr bounds, and metabox-backed shared xattrs still require dedicated tests.
|
||||
- Multi-device work is outside this ACL/xattr integration batch.
|
||||
@@ -0,0 +1,112 @@
|
||||
# repo22 Cold Nested Namei Regression Report
|
||||
|
||||
- Date: 2026-08-08 UTC
|
||||
- Baseline: `29a215dd4519579f6313b3df67d524a6b4bdf3ca`
|
||||
- Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
- Final module SHA-256:
|
||||
`50a19ea96c7414f73d92049671e66a9eacc9ff5abe27950a46d16c8e6c763baf`
|
||||
- Result: PASS
|
||||
|
||||
## Root Cause
|
||||
|
||||
FreeBSD pathname lookup passes a component as `cn_nameptr` plus
|
||||
`cn_namelen`. An intermediate component is followed by `/` in the pathname
|
||||
buffer and is not NUL-terminated at `cn_namelen`. The imported Linux EROFS
|
||||
comparison assumed Linux dentry-name termination, ignored the supplied length,
|
||||
and tested `qn_name[i] == '\0'` after matching the on-disk name. Therefore a
|
||||
final component worked, while the same name used as an intermediate component
|
||||
compared greater than the on-disk entry and returned `ENOENT`. Looking up the
|
||||
parent as a final component first populated the FreeBSD namecache and hid the
|
||||
bug on the next nested lookup.
|
||||
|
||||
The old error path also inserted a negative cache entry for every lookup
|
||||
error, including integrity and I/O errors, which could mask later corruption
|
||||
as `ENOENT`.
|
||||
|
||||
## Implementation
|
||||
|
||||
- `src/namei.c`
|
||||
- Compares pathname components by explicit length without reading beyond
|
||||
`cn_namelen`.
|
||||
- Uses unsigned-byte ordering compatible with EROFS directory sorting.
|
||||
- Validates the minimum block size before reading the first dirent.
|
||||
- Validates the dirent-array boundary, strictly increasing name offsets,
|
||||
name-slot bounds, name length, and zero-only NUL padding.
|
||||
- Inserts negative namecache entries only for real `ENOENT` misses.
|
||||
- `src/dir.c`
|
||||
- Applies the same directory-block and name-padding validation to `readdir`.
|
||||
- Determines the actual last-name length before enforcing `EROFS_NAME_LEN`,
|
||||
so valid full-block zero padding is accepted.
|
||||
- Keeps on-disk offsets unchanged and appends a synthetic `.` at `i_size`
|
||||
for `dot_omitted`, matching Linux EROFS.
|
||||
- Aligns restart positions relative to each directory block and preserves
|
||||
the `i_size` cookie needed to resume the synthetic dot entry.
|
||||
- Initializes returned cookie-array outputs before allocation.
|
||||
|
||||
## Deterministic Fixture
|
||||
|
||||
`prepare-fixtures.sh` creates the same image twice and requires `cmp` success.
|
||||
The base image contains a cold multi-level path and a 320-file, multi-block
|
||||
directory. Fixed timestamp, ownership, UUID, worker count, and uncompressed
|
||||
layout are used.
|
||||
|
||||
Final image hashes:
|
||||
|
||||
```text
|
||||
2e9fd75159011ced31646a38f942fa0822d5b3e8e19b7df55b844575fba991ea corrupt-nameoff.erofs
|
||||
63d12232f9ea0dc7f7ff477d20b95a8412d191fc10bf4b67dacc47e050c379fb corrupt-padding.erofs
|
||||
77f8a56f969e173d291ccbd957821f1ba284d3e54d875dbcdb9bc398024dfa5a corrupt-short-block.erofs
|
||||
11064ffbb814ff41027aebc8f36e56d2f24affb7b50836948b3ffbf6d79dee0e dot-omitted.erofs
|
||||
7a2c244ec10e9a43531b0e8b92e26b11f52d67bab00528b8ba2f584e20e57420 nested-repeat.erofs
|
||||
7a2c244ec10e9a43531b0e8b92e26b11f52d67bab00528b8ba2f584e20e57420 nested.erofs
|
||||
```
|
||||
|
||||
## Build Results
|
||||
|
||||
- `./build.sh`: PASS.
|
||||
- Final `build/erofs.ko` SHA-256 remained
|
||||
`50a19ea96c7414f73d92049671e66a9eacc9ff5abe27950a46d16c8e6c763baf`.
|
||||
- `nm -u build/erofs.ko | grep -w bcmp`: no match.
|
||||
- `git diff --check` for all scoped source and test files: PASS.
|
||||
|
||||
The final integration rerun used the same combined module and these guest
|
||||
commands:
|
||||
|
||||
```sh
|
||||
cd /root/repo22-namei
|
||||
cc -Wall -Wextra -O2 -o readdir_probe readdir_probe.c
|
||||
./vm-regression.sh
|
||||
```
|
||||
|
||||
`vm-regression.sh` performs `kldload`, creates each vnode-backed md device,
|
||||
mounts it with `mount -t erofs`, executes the cold lookup and readdir probes,
|
||||
unmounts and detaches each image, and finishes with exact module unload.
|
||||
|
||||
## FreeBSD VM Results
|
||||
|
||||
- `kldload`: PASS.
|
||||
- Cold direct read of
|
||||
`/alpha/bravo/charlie/payload.txt` without parent lookup or `readdir`: PASS.
|
||||
- Repeated lookup and sibling nested lookup: PASS.
|
||||
- Two negative lookups followed by an existing nested lookup: PASS.
|
||||
- Multi-block `wide` readdir: 322 dirents (`.`, `..`, 320 files), PASS.
|
||||
- Resume from every one of the 322 returned `d_off` cookies: PASS.
|
||||
- `dot_omitted` root cookies: `12`, `24`, `47`, `48`; resume at `47`
|
||||
returns only `.`, and resume at `48` returns EOF: PASS.
|
||||
- Short directory block: two lookups and direct `getdirentries` all return
|
||||
`EINTEGRITY`, PASS.
|
||||
- Non-monotonic `nameoff`: two lookups and direct `getdirentries` all return
|
||||
`EINTEGRITY`, PASS.
|
||||
- Nonzero data after NUL padding: two lookups and direct `getdirentries` all
|
||||
return `EINTEGRITY`, PASS.
|
||||
- `kldunload`: PASS.
|
||||
- Post-test EROFS module, mount, and md-device state: clean.
|
||||
- Final explicit state check: zero EROFS modules, zero EROFS mounts, zero md
|
||||
devices, and no recent panic or fatal trap in dmesg.
|
||||
|
||||
## Remaining Scope
|
||||
|
||||
No unresolved issue remains for the requested cold lookup and directory
|
||||
regression. The NFS-specific `a_cookies` consumer path was not exercised by an
|
||||
NFS export; the tested `d_off` restart-cookie sequence uses the same generated
|
||||
cookie values.
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
fixture_dir="$script_dir/fixture"
|
||||
artifact_dir="$script_dir/artifacts"
|
||||
|
||||
mkdir -p \
|
||||
"$fixture_dir/alpha/bravo/charlie" \
|
||||
"$fixture_dir/alpha/sibling" \
|
||||
"$fixture_dir/wide" \
|
||||
"$artifact_dir"
|
||||
|
||||
printf '%s\n' 'cold nested lookup payload' > \
|
||||
"$fixture_dir/alpha/bravo/charlie/payload.txt"
|
||||
printf '%s\n' 'repeat lookup payload' > \
|
||||
"$fixture_dir/alpha/bravo/repeat.txt"
|
||||
printf '%s\n' 'sibling marker' > \
|
||||
"$fixture_dir/alpha/sibling/marker.txt"
|
||||
|
||||
index=0
|
||||
while [ "$index" -lt 320 ]; do
|
||||
name=$(printf 'entry-%03d-abcdefghijklmnopqrstuvwxyz.txt' "$index")
|
||||
printf 'wide entry %03d\n' "$index" > "$fixture_dir/wide/$name"
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
find "$fixture_dir" -exec touch -h -t 197001010000.00 {} +
|
||||
|
||||
build_image()
|
||||
{
|
||||
image=$1
|
||||
mkfs.erofs -d0 -x-1 -T0 --all-time --all-root --workers=1 \
|
||||
-U 11111111-2222-3333-4444-555555555555 \
|
||||
"$image" "$fixture_dir"
|
||||
}
|
||||
|
||||
build_image "$artifact_dir/nested.erofs"
|
||||
build_image "$artifact_dir/nested-repeat.erofs"
|
||||
cmp "$artifact_dir/nested.erofs" "$artifact_dir/nested-repeat.erofs"
|
||||
|
||||
ARTIFACT_DIR="$artifact_dir" python3 <<'PY'
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
|
||||
artifact_dir = os.environ["ARTIFACT_DIR"]
|
||||
base_path = os.path.join(artifact_dir, "nested.erofs")
|
||||
|
||||
|
||||
def u16(image, offset):
|
||||
return struct.unpack_from("<H", image, offset)[0]
|
||||
|
||||
|
||||
def u32(image, offset):
|
||||
return struct.unpack_from("<I", image, offset)[0]
|
||||
|
||||
|
||||
def put_u16(image, offset, value):
|
||||
struct.pack_into("<H", image, offset, value)
|
||||
|
||||
|
||||
def put_u32(image, offset, value):
|
||||
struct.pack_into("<I", image, offset, value)
|
||||
|
||||
|
||||
def update_superblock_checksum(image):
|
||||
polynomial = 0x82F63B78
|
||||
checksum = 0xFFFFFFFF
|
||||
put_u32(image, 1024 + 4, 0)
|
||||
for byte in image[1024:4096]:
|
||||
checksum ^= byte
|
||||
for _ in range(8):
|
||||
checksum = (checksum >> 1) ^ (polynomial if checksum & 1 else 0)
|
||||
put_u32(image, 1024 + 4, checksum & 0xFFFFFFFF)
|
||||
|
||||
|
||||
def inode_offset(nid):
|
||||
return nid << 5
|
||||
|
||||
|
||||
def compact_inline_dir(image, nid):
|
||||
offset = inode_offset(nid)
|
||||
inode_format = u16(image, offset)
|
||||
layout = (inode_format >> 1) & 0x7
|
||||
assert (inode_format & 0x1) == 0
|
||||
assert layout == 2
|
||||
assert u16(image, offset + 2) == 0
|
||||
return offset, offset + 32, u32(image, offset + 8)
|
||||
|
||||
|
||||
def dir_entries(image, data_offset, size):
|
||||
first_nameoff = u16(image, data_offset + 8)
|
||||
assert first_nameoff >= 12
|
||||
assert first_nameoff % 12 == 0
|
||||
assert first_nameoff < size
|
||||
count = first_nameoff // 12
|
||||
entries = []
|
||||
for index in range(count):
|
||||
entry_offset = data_offset + index * 12
|
||||
nid, nameoff, file_type, reserved = struct.unpack_from(
|
||||
"<QHBB", image, entry_offset
|
||||
)
|
||||
endoff = (
|
||||
u16(image, data_offset + (index + 1) * 12 + 8)
|
||||
if index + 1 < count
|
||||
else size
|
||||
)
|
||||
assert first_nameoff <= nameoff < endoff <= size
|
||||
slot = bytes(image[data_offset + nameoff : data_offset + endoff])
|
||||
name = slot.split(b"\0", 1)[0]
|
||||
assert name
|
||||
entries.append((nid, nameoff, file_type, reserved, name))
|
||||
return entries
|
||||
|
||||
|
||||
with open(base_path, "rb") as source:
|
||||
base = bytearray(source.read())
|
||||
|
||||
assert u32(base, 1024) == 0xE0F5E1E2
|
||||
assert base[1024 + 12] == 12
|
||||
assert u16(base, 1024 + 14) == 36
|
||||
root_nid = 36
|
||||
root_inode, root_data, root_size = compact_inline_dir(base, root_nid)
|
||||
root_entries = dir_entries(base, root_data, root_size)
|
||||
assert [entry[4] for entry in root_entries] == [b".", b"..", b"alpha", b"wide"]
|
||||
|
||||
dot_omitted = bytearray(base)
|
||||
removed = 13
|
||||
new_size = root_size - removed
|
||||
new_entries = root_entries[1:]
|
||||
new_first_nameoff = len(new_entries) * 12
|
||||
for index, entry in enumerate(new_entries):
|
||||
nid, nameoff, file_type, reserved, _ = entry
|
||||
struct.pack_into(
|
||||
"<QHBB",
|
||||
dot_omitted,
|
||||
root_data + index * 12,
|
||||
nid,
|
||||
nameoff - removed,
|
||||
file_type,
|
||||
reserved,
|
||||
)
|
||||
names = b"".join(entry[4] for entry in new_entries)
|
||||
dot_omitted[root_data + new_first_nameoff : root_data + new_size] = names
|
||||
dot_omitted[root_data + new_size : root_data + root_size] = b"\0" * (
|
||||
root_size - new_size
|
||||
)
|
||||
put_u16(dot_omitted, root_inode, u16(dot_omitted, root_inode) | (1 << 4))
|
||||
put_u32(dot_omitted, root_inode + 8, new_size)
|
||||
assert [entry[4] for entry in dir_entries(dot_omitted, root_data, new_size)] == [
|
||||
b"..",
|
||||
b"alpha",
|
||||
b"wide",
|
||||
]
|
||||
|
||||
short_block = bytearray(base)
|
||||
put_u32(short_block, root_inode + 8, 8)
|
||||
|
||||
nonmonotonic = bytearray(base)
|
||||
first_nameoff = u16(nonmonotonic, root_data + 8)
|
||||
put_u16(nonmonotonic, root_data + 12 + 8, first_nameoff)
|
||||
|
||||
wide_nid = next(entry[0] for entry in root_entries if entry[4] == b"wide")
|
||||
wide_inode = inode_offset(wide_nid)
|
||||
assert ((u16(base, wide_inode) >> 1) & 0x7) == 2
|
||||
wide_startblk = u32(base, wide_inode + 16)
|
||||
wide_block = wide_startblk << 12
|
||||
wide_first_nameoff = u16(base, wide_block + 8)
|
||||
wide_count = wide_first_nameoff // 12
|
||||
wide_last_nameoff = u16(base, wide_block + (wide_count - 1) * 12 + 8)
|
||||
padding_nul = base.index(0, wide_block + wide_last_nameoff, wide_block + 4096)
|
||||
assert padding_nul + 1 < wide_block + 4096
|
||||
assert base[padding_nul + 1] == 0
|
||||
bad_padding = bytearray(base)
|
||||
bad_padding[padding_nul + 1] = ord("X")
|
||||
|
||||
outputs = {
|
||||
"dot-omitted.erofs": dot_omitted,
|
||||
"corrupt-short-block.erofs": short_block,
|
||||
"corrupt-nameoff.erofs": nonmonotonic,
|
||||
"corrupt-padding.erofs": bad_padding,
|
||||
}
|
||||
for filename, image in outputs.items():
|
||||
update_superblock_checksum(image)
|
||||
path = os.path.join(artifact_dir, filename)
|
||||
with open(path, "wb") as output:
|
||||
output.write(image)
|
||||
|
||||
with open(os.path.join(artifact_dir, "SHA256SUMS"), "w", encoding="ascii") as sums:
|
||||
for filename in sorted(["nested.erofs", "nested-repeat.erofs", *outputs]):
|
||||
path = os.path.join(artifact_dir, filename)
|
||||
with open(path, "rb") as image_file:
|
||||
digest = hashlib.sha256(image_file.read()).hexdigest()
|
||||
sums.write(f"{digest} {filename}\n")
|
||||
PY
|
||||
|
||||
cat "$artifact_dir/SHA256SUMS"
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/dirent.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
struct dirent *entry;
|
||||
off_t base, before, start;
|
||||
char *buffer;
|
||||
char *end;
|
||||
size_t buffer_size;
|
||||
ssize_t bytes;
|
||||
int calls, fd;
|
||||
|
||||
if (argc < 2 || argc > 5)
|
||||
errx(2, "usage: %s directory [offset [buffer-size [calls]]]",
|
||||
argv[0]);
|
||||
start = argc >= 3 ? strtoll(argv[2], NULL, 0) : 0;
|
||||
buffer_size = argc >= 4 ? strtoul(argv[3], NULL, 0) : 128;
|
||||
calls = argc >= 5 ? strtol(argv[4], NULL, 0) : 32;
|
||||
if (buffer_size < 32 || calls < 1)
|
||||
errx(2, "invalid buffer size or call count");
|
||||
|
||||
fd = open(argv[1], O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0)
|
||||
err(1, "open %s", argv[1]);
|
||||
if (lseek(fd, start, SEEK_SET) < 0)
|
||||
err(1, "lseek %jd", (intmax_t)start);
|
||||
buffer = malloc(buffer_size);
|
||||
if (buffer == NULL)
|
||||
err(1, "malloc");
|
||||
|
||||
for (int call = 0; call < calls; call++) {
|
||||
before = lseek(fd, 0, SEEK_CUR);
|
||||
if (before < 0)
|
||||
err(1, "lseek current");
|
||||
base = -1;
|
||||
bytes = getdirentries(fd, buffer, buffer_size, &base);
|
||||
if (bytes < 0)
|
||||
err(1, "getdirentries");
|
||||
printf("call=%d before=%jd after=%jd base=%jd bytes=%zd\n",
|
||||
call, (intmax_t)before,
|
||||
(intmax_t)lseek(fd, 0, SEEK_CUR), (intmax_t)base, bytes);
|
||||
if (bytes == 0)
|
||||
break;
|
||||
end = buffer + bytes;
|
||||
for (entry = (struct dirent *)buffer;
|
||||
(char *)entry < end;
|
||||
entry = (struct dirent *)((char *)entry + entry->d_reclen)) {
|
||||
if (entry->d_reclen == 0 ||
|
||||
(char *)entry + entry->d_reclen > end)
|
||||
errx(1, "invalid dirent record");
|
||||
printf(" off=%jd ino=%ju reclen=%u type=%u name=%.*s\n",
|
||||
(intmax_t)entry->d_off, (uintmax_t)entry->d_fileno,
|
||||
entry->d_reclen, entry->d_type, entry->d_namlen,
|
||||
entry->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
close(fd);
|
||||
return (0);
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
#!/bin/sh
|
||||
set -u
|
||||
|
||||
test_dir=/root/repo22-namei
|
||||
mount_dir=/mnt/repo22-namei
|
||||
module_id=
|
||||
md_device=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
set +e
|
||||
if mount | grep -q " on $mount_dir "; then
|
||||
umount "$mount_dir"
|
||||
fi
|
||||
if [ -n "$md_device" ]; then
|
||||
mdconfig -d -u "${md_device#md}"
|
||||
fi
|
||||
if [ -n "$module_id" ] && kldstat -q -i "$module_id"; then
|
||||
kldunload -i "$module_id"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
mount_image()
|
||||
{
|
||||
image=$1
|
||||
md_device=$(mdconfig -a -t vnode -f "$test_dir/$image") ||
|
||||
fail "mdconfig $image"
|
||||
mount -t erofs "/dev/$md_device" "$mount_dir" ||
|
||||
fail "mount $image"
|
||||
echo "mounted image=$image device=$md_device"
|
||||
}
|
||||
|
||||
unmount_image()
|
||||
{
|
||||
umount "$mount_dir" || fail "umount $md_device"
|
||||
mdconfig -d -u "${md_device#md}" || fail "detach $md_device"
|
||||
md_device=
|
||||
}
|
||||
|
||||
expect_integrity_failure()
|
||||
{
|
||||
image=$1
|
||||
lookup_path=$2
|
||||
readdir_path=$3
|
||||
label=${image%.erofs}
|
||||
|
||||
mount_image "$image"
|
||||
attempt=1
|
||||
while [ "$attempt" -le 2 ]; do
|
||||
output="$test_dir/$label-lookup-$attempt.txt"
|
||||
if stat "$mount_dir/$lookup_path" >"$output" 2>&1; then
|
||||
fail "$image lookup attempt $attempt unexpectedly succeeded"
|
||||
fi
|
||||
if grep -qi "No such file" "$output"; then
|
||||
fail "$image lookup attempt $attempt became ENOENT"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
output="$test_dir/$label-readdir.txt"
|
||||
if ./readdir_probe "$mount_dir/$readdir_path" 0 512 2 \
|
||||
>"$output" 2>&1; then
|
||||
fail "$image readdir unexpectedly succeeded"
|
||||
fi
|
||||
unmount_image
|
||||
echo "integrity image=$image repeated-lookup=error readdir=error"
|
||||
}
|
||||
|
||||
cd "$test_dir" || exit 1
|
||||
mkdir -p "$mount_dir"
|
||||
|
||||
if mount | grep -qi erofs; then
|
||||
fail "pre-existing EROFS mount"
|
||||
fi
|
||||
if kldstat | grep -qi erofs; then
|
||||
fail "pre-existing EROFS module"
|
||||
fi
|
||||
if [ -n "$(mdconfig -l)" ]; then
|
||||
fail "pre-existing md device"
|
||||
fi
|
||||
|
||||
echo "== guest =="
|
||||
uname -a
|
||||
date -u
|
||||
|
||||
echo "== module load =="
|
||||
kldload "$test_dir/erofs.ko" || fail "kldload"
|
||||
module_id=$(kldstat | awk '$NF == "erofs.ko" { print $1 }')
|
||||
[ -n "$module_id" ] || fail "loaded module not found"
|
||||
kldstat -v -i "$module_id"
|
||||
|
||||
echo "== cold nested lookup =="
|
||||
mount_image nested.erofs
|
||||
payload=$(cat "$mount_dir/alpha/bravo/charlie/payload.txt") ||
|
||||
fail "cold nested lookup"
|
||||
[ "$payload" = "cold nested lookup payload" ] || fail "cold payload mismatch"
|
||||
payload=$(cat "$mount_dir/alpha/bravo/charlie/payload.txt") ||
|
||||
fail "repeated nested lookup"
|
||||
[ "$payload" = "cold nested lookup payload" ] || fail "repeat payload mismatch"
|
||||
payload=$(cat "$mount_dir/alpha/bravo/repeat.txt") ||
|
||||
fail "sibling nested lookup"
|
||||
[ "$payload" = "repeat lookup payload" ] || fail "sibling payload mismatch"
|
||||
|
||||
attempt=1
|
||||
while [ "$attempt" -le 2 ]; do
|
||||
output="$test_dir/missing-$attempt.txt"
|
||||
if stat "$mount_dir/alpha/bravo/missing" >"$output" 2>&1; then
|
||||
fail "missing lookup attempt $attempt unexpectedly succeeded"
|
||||
fi
|
||||
grep -qi "No such file" "$output" || fail "missing lookup was not ENOENT"
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
payload=$(cat "$mount_dir/alpha/bravo/charlie/payload.txt") ||
|
||||
fail "existing lookup after negative cache"
|
||||
[ "$payload" = "cold nested lookup payload" ] || fail "post-negative payload mismatch"
|
||||
echo "cold lookup=pass repeat=pass negative-cache=pass"
|
||||
|
||||
echo "== large readdir and cookies =="
|
||||
wide_count=$(ls -A1 "$mount_dir/wide" | wc -l | tr -d ' ')
|
||||
[ "$wide_count" = 320 ] || fail "wide entry count $wide_count"
|
||||
./readdir_probe "$mount_dir/wide" 0 128 400 > wide-probe.txt ||
|
||||
fail "wide readdir probe"
|
||||
awk '/^ off=/ {
|
||||
off = $1; sub(/^off=/, "", off);
|
||||
name = $5; sub(/^name=/, "", name);
|
||||
print off, name;
|
||||
}' wide-probe.txt > wide-cookies.txt
|
||||
wide_dirents=$(wc -l < wide-cookies.txt | tr -d ' ')
|
||||
[ "$wide_dirents" = 322 ] || fail "wide dirent count $wide_dirents"
|
||||
awk '{ cookie[NR] = $1; name[NR] = $2 }
|
||||
END {
|
||||
for (i = 1; i <= NR; i++)
|
||||
print cookie[i], (i < NR ? name[i + 1] : "<EOF>");
|
||||
}' wide-cookies.txt > wide-resume-cases.txt
|
||||
while read -r cookie expected; do
|
||||
./readdir_probe "$mount_dir/wide" "$cookie" 128 1 > wide-resume.txt ||
|
||||
fail "resume cookie $cookie"
|
||||
if [ "$expected" = "<EOF>" ]; then
|
||||
grep -q 'bytes=0$' wide-resume.txt || fail "cookie $cookie not EOF"
|
||||
else
|
||||
actual=$(awk '/^ off=/ {
|
||||
name = $5; sub(/^name=/, "", name); print name; exit;
|
||||
}' wide-resume.txt)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "cookie $cookie expected $expected got $actual"
|
||||
fi
|
||||
done < wide-resume-cases.txt
|
||||
echo "wide entries=$wide_dirents all-resume-cookies=pass"
|
||||
unmount_image
|
||||
|
||||
echo "== dot omitted cookies =="
|
||||
mount_image dot-omitted.erofs
|
||||
./readdir_probe "$mount_dir" 0 512 8 > dot-probe.txt || fail "dot probe"
|
||||
awk '/^ off=/ {
|
||||
off = $1; sub(/^off=/, "", off);
|
||||
name = $5; sub(/^name=/, "", name);
|
||||
print off, name;
|
||||
}' dot-probe.txt > dot-cookies.txt
|
||||
cat > dot-expected.txt <<'EOF'
|
||||
12 ..
|
||||
24 alpha
|
||||
47 wide
|
||||
48 .
|
||||
EOF
|
||||
cmp dot-cookies.txt dot-expected.txt || fail "dot cookie sequence"
|
||||
./readdir_probe "$mount_dir" 47 128 1 > dot-resume-47.txt || fail "dot resume 47"
|
||||
grep -q '^ off=48 .* name=\.$' dot-resume-47.txt || fail "dot resume 47 result"
|
||||
./readdir_probe "$mount_dir" 48 128 1 > dot-resume-48.txt || fail "dot resume 48"
|
||||
grep -q 'bytes=0$' dot-resume-48.txt || fail "dot resume 48 not EOF"
|
||||
echo "dot cookies=12,24,47,48 resume=pass"
|
||||
unmount_image
|
||||
|
||||
echo "== corrupted directories =="
|
||||
expect_integrity_failure corrupt-short-block.erofs alpha .
|
||||
expect_integrity_failure corrupt-nameoff.erofs alpha .
|
||||
expect_integrity_failure \
|
||||
corrupt-padding.erofs \
|
||||
wide/entry-000-abcdefghijklmnopqrstuvwxyz.txt \
|
||||
wide
|
||||
|
||||
echo "== module unload =="
|
||||
kldunload -i "$module_id" || fail "kldunload"
|
||||
module_id=
|
||||
if kldstat | grep -qi erofs; then
|
||||
fail "module remains loaded"
|
||||
fi
|
||||
if mount | grep -qi erofs; then
|
||||
fail "mount remains"
|
||||
fi
|
||||
if [ -n "$(mdconfig -l)" ]; then
|
||||
fail "md remains"
|
||||
fi
|
||||
|
||||
dmesg | tail -120 > dmesg-tail.txt
|
||||
echo "PASS: all VM regressions"
|
||||
@@ -0,0 +1,113 @@
|
||||
# repo22 xattr/ACL root-cause manual test report
|
||||
|
||||
Date: 2026-08-08 18:12 UTC; final integration rerun on 2026-08-08
|
||||
Baseline: `29a215dd4519579f6313b3df67d524a6b4bdf3ca` plus the scoped xattr/ACL fixes
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64
|
||||
Host tools: erofs-utils 1.8.6 plus deterministic transformed fixtures
|
||||
|
||||
## Build and module
|
||||
|
||||
- `git diff --check`: PASS.
|
||||
- `./build.sh`: PASS; final `build/erofs.ko` SHA-256 is
|
||||
`50a19ea96c7414f73d92049671e66a9eacc9ff5abe27950a46d16c8e6c763baf`.
|
||||
- `nm -u build/erofs.ko | grep -w bcmp`: no match, PASS.
|
||||
- `kldload /tmp/repo22-integration-20260808/erofs.ko`: PASS.
|
||||
- Repeated `mdconfig -a -t vnode -f IMAGE`, `mount -t erofs`, `umount`, and
|
||||
`mdconfig -d`: PASS for every fixture below.
|
||||
- `kldunload erofs`: PASS; final module, EROFS mount, and md-device counts were
|
||||
all zero.
|
||||
- New dmesg errors, traps, or panics: none.
|
||||
|
||||
## Existing xattr and ACL regression
|
||||
|
||||
- Inline user, trusted, and security xattrs: PASS.
|
||||
- Shared user xattrs and shared POSIX access ACL: PASS.
|
||||
- Packed long-prefix table and xattr-name-filter: PASS.
|
||||
- Access/default ACL decode and inherited child ACL: PASS.
|
||||
- Owner, owning-group, named-user, and other access matrix: PASS.
|
||||
- Read-only ACL/xattr mutation rejection: PASS.
|
||||
- TC117 malformed name length and value size: PASS. Xattr operations returned
|
||||
`EINTEGRITY`; directory metadata and file contents remained readable.
|
||||
- An invalid long-prefix reference without a declared prefix table is now
|
||||
skipped like Linux. Other valid xattrs and file data remain readable.
|
||||
|
||||
## New regression cases
|
||||
|
||||
- TC134 metabox shared nonzero base: PASS with
|
||||
`metabox-shared-nonzero-base.erofs`. The image has
|
||||
`SHARED_EA_IN_METABOX`, `xattr_blkaddr=1`, and a plain metabox carrier.
|
||||
`getextattr -qq user metaboxshared /mnt/repo22-integration/hello.txt`
|
||||
returned exactly `nonzero-base`.
|
||||
- TC135 metabox long-prefix backing: PASS with
|
||||
`metabox-prefix-shared.erofs`.
|
||||
`getextattr -qq user repo22.application.component.setting` on
|
||||
`/mnt/repo22-integration/long/file2.txt` returned `long-prefix-value`.
|
||||
Primary-image long-prefix fallback also passed with
|
||||
`long-prefix-primary-fallback.erofs` and the same expected value.
|
||||
- TC136 truncated metabox extension: PASS; mount returned `EINTEGRITY`.
|
||||
Out-of-range ishare prefix ID: PASS; mount returned `EINTEGRITY`.
|
||||
- TC137 shared xattr outside declared image: PASS; normal data and an in-bounds
|
||||
xattr remained readable, while the redirected entry returned `EINTEGRITY`.
|
||||
Prefix record outside declared image: PASS; mount failed. The valid primary
|
||||
fallback control mounted and returned the expected value.
|
||||
- TC138 unordered unique UID qualifiers: PASS and preserved order 3002, 2002.
|
||||
Header-only ACL: PASS and fell back to mode. Duplicate UID qualifier: PASS
|
||||
negative test and returned `EINTEGRITY`.
|
||||
- TC139 FIFO access ACL, list/get system xattr: PASS. `setfacl`, `setextattr`,
|
||||
and `rmextattr` all returned read-only filesystem errors.
|
||||
- TC140 compressed metabox: PASS with `metabox-large-shared.erofs`. On-disk
|
||||
qualification found `METABOX`, metabox inode datalayout 1
|
||||
(`EROFS_INODE_COMPRESSED_FULL`), and no fragment pcluster. A cold file read,
|
||||
the metabox long-prefix lookup, and a 231-byte xattr value all matched the
|
||||
source fixture.
|
||||
|
||||
## Final metabox and metadata-boundary smoke
|
||||
|
||||
The final combined module was exercised with these exact guest operations:
|
||||
|
||||
```sh
|
||||
kldload /tmp/repo22-integration-20260808/erofs.ko
|
||||
mdconfig -a -t vnode -f /tmp/repo22-integration-20260808/IMAGE.erofs
|
||||
mount -t erofs /dev/md0 /mnt/repo22-integration
|
||||
getextattr -qq user NAME /mnt/repo22-integration/PATH
|
||||
umount /mnt/repo22-integration
|
||||
mdconfig -d -u 0
|
||||
kldunload erofs
|
||||
```
|
||||
|
||||
- Compressed metabox and metabox long-prefix:
|
||||
`metabox-large-shared.erofs` returned `long-prefix-value` for
|
||||
`repo22.application.component.setting`; its regular file content also
|
||||
matched `long prefix fixture 1`.
|
||||
- Primary shared fallback while `METABOX` is enabled:
|
||||
`metabox-large-shared.erofs` has `SHARED_EA_IN_METABOX` clear and
|
||||
`xattr_blkaddr=1`; `shared_key` returned `repo22-shared-value` from the
|
||||
primary shared-xattr area.
|
||||
- `SHARED_EA_IN_METABOX` nonzero base:
|
||||
`metabox-shared-nonzero-base.erofs` returned `nonzero-base` from the shared
|
||||
entry addressed relative to metabox block 1.
|
||||
- Cross-metadata shared xattr: `shared-cross-metadata.erofs` returned
|
||||
`repo22-shared-value` for `shared_key` on `shared/file3.txt`.
|
||||
- Cross-metadata inline xattr: `xattr-cross-metadata.erofs` returned the exact
|
||||
expected `alpha`, `gamma`, and `delta` values on `file000`, `file090`, and
|
||||
`file179`. The metabox-specific inline boundary fixture also returned
|
||||
`valid-inline-boundary` for `crossboundary`.
|
||||
|
||||
The fragment-backed compressed metabox negative layout was **not generated and
|
||||
was not executed**. The passing TC140 fixture is compressed but non-fragment.
|
||||
The code still rejects a compressed metabox inode whose fragment flag is set;
|
||||
that rejection remains layout-reviewed rather than fixture-verified.
|
||||
|
||||
## Fixture qualification
|
||||
|
||||
The old `namespace-shared.erofs` contains an xattr entry with name index `0x80`
|
||||
but advertises no `XATTR_PREFIXES` feature and contains no prefix table. It is
|
||||
not a valid long-prefix fixture. The driver now follows Linux behavior by
|
||||
skipping that unresolved entry while preserving all valid xattrs and file data.
|
||||
|
||||
## Separate namei write set
|
||||
|
||||
The previously observed cold nested lookup issue was fixed by the separate
|
||||
`src/namei.c` and `src/dir.c` write set and passed TC141 in the same final
|
||||
integration build. Those source files are intentionally excluded from the
|
||||
xattr/ACL commit.
|
||||
@@ -0,0 +1,232 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,559 @@
|
||||
# repo22 FreeBSD 15 NFS export manual test report
|
||||
|
||||
Date: 2026-08-08 20:08-20:37 UTC
|
||||
|
||||
Baseline: `4ef680df6dcbfad329c0b2b1c0af8fcb21cbed4a` plus this NFS export batch
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64,
|
||||
`releng/15.0-n281036-53054229dcb3`, QEMU TCG, 4 vCPUs, 6144 MB RAM
|
||||
|
||||
Host tools: erofs-utils 1.8.6, clang cross-target build
|
||||
|
||||
## Result
|
||||
|
||||
**PASS** for the implementation and all reasonably executable NFS tests.
|
||||
|
||||
FreeBSD mountd installed real EROFS exports through export-only `MNT_UPDATE`;
|
||||
NFSv3 clients resolved 16-byte EROFS handles for regular files, directories,
|
||||
symlinks, FIFOs, metabox bit-63 inodes, and multidevice files. Handle mutation,
|
||||
same-md remount stability, nfsd restart, 12,050-entry READDIRPLUS pagination,
|
||||
cold remounts, concurrent traversal/read/stat load, read-only enforcement, and
|
||||
device-boundary errors all behaved as required.
|
||||
|
||||
The only non-code issue was a cleanup-order mistake after the completed test
|
||||
matrix: one hard loopback NFS mount remained while nfsd and the direct EROFS
|
||||
mount were stopped. This blocked `mount -p` and later produced five client
|
||||
`fileid changed` messages when nfsd was temporarily restarted without the
|
||||
underlying export. The functional-test dmesg snapshot taken before that cleanup
|
||||
mistake was byte-identical to its baseline. The VM was rebooted to clear the
|
||||
blocked cleanup process, and a final correctly ordered NFS rerun completed with
|
||||
byte-identical pre/post dmesg and zero remaining resources.
|
||||
|
||||
## Repository and build
|
||||
|
||||
Initial verification:
|
||||
|
||||
```sh
|
||||
git -C /work/repo-community/repo22 fetch xdm main
|
||||
git -C /work/repo-community/repo22 rev-parse HEAD
|
||||
git -C /work/repo-community/repo22 rev-parse xdm/main
|
||||
git -C /work/repo-community/repo22 diff --quiet
|
||||
git -C /work/repo-community/repo22 diff --cached --quiet
|
||||
```
|
||||
|
||||
Both revisions were:
|
||||
|
||||
```text
|
||||
4ef680df6dcbfad329c0b2b1c0af8fcb21cbed4a
|
||||
```
|
||||
|
||||
Tracked files were clean. Existing untracked `repo22/build`,
|
||||
`tests/results/manual/2026-08-08T1800Z-namei/artifacts`, and files outside
|
||||
repo22 were not deleted, modified, staged, or committed.
|
||||
|
||||
To execute the required `./build.sh` without overwriting the pre-existing
|
||||
untracked build products, the current source was copied to an isolated build
|
||||
directory:
|
||||
|
||||
```sh
|
||||
BUILD_ROOT=$(mktemp -d /work/build/repo22-nfs-build.XXXXXX)
|
||||
mkdir -p "$BUILD_ROOT/src"
|
||||
cp repo-community/repo22/build.sh "$BUILD_ROOT/build.sh"
|
||||
cp repo-community/repo22/src/* "$BUILD_ROOT/src/"
|
||||
chmod +x "$BUILD_ROOT/build.sh"
|
||||
cd "$BUILD_ROOT"
|
||||
./build.sh
|
||||
nm -u build/erofs.ko | \
|
||||
awk '$NF == "bcmp" { found = 1 } END { if (found) exit 1 }'
|
||||
sha256sum build/erofs.ko
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
==> SUCCESS: /work/build/repo22-nfs-build.eFulVf/build/erofs.ko
|
||||
48776485d1679f00100bc7b2be5f891c8f03a3e8b53ac7c00da8c2fd5f74cc70 erofs.ko
|
||||
```
|
||||
|
||||
There was no unresolved `bcmp` reference.
|
||||
|
||||
The FreeBSD helper compiled without warnings:
|
||||
|
||||
```sh
|
||||
cc -O2 -Wall -Wextra -std=c17 nfs_fh_tool.c -o nfs_fh_tool
|
||||
```
|
||||
|
||||
## Fixtures
|
||||
|
||||
The primary fixture was generated with:
|
||||
|
||||
```sh
|
||||
mkdir -p plain-src/basic/subdir plain-src/bigdir plain-src/concurrent
|
||||
printf 'repo22-nfs-regular\nline-two\n' > plain-src/basic/regular.txt
|
||||
printf 'nested-directory-file\n' > plain-src/basic/subdir/nested.txt
|
||||
ln -s regular.txt plain-src/basic/link-to-regular
|
||||
mkfifo plain-src/basic/test.fifo
|
||||
for i in $(seq -w 0 12049); do
|
||||
printf 'entry-%s\n' "$i" > plain-src/bigdir/file-$i
|
||||
done
|
||||
for i in $(seq -w 0 255); do
|
||||
awk -v n="$i" 'BEGIN {
|
||||
for (j = 0; j < 256; j++)
|
||||
printf "worker-%s-line-%03d-abcdefghijklmnopqrstuvwxyz0123456789\\n", n, j
|
||||
}' > plain-src/concurrent/file-$i.dat
|
||||
done
|
||||
awk 'BEGIN {
|
||||
for (i = 0; i < 262144; i++)
|
||||
printf "throughput-%08d-abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\\n", i
|
||||
}' > plain-src/throughput.dat
|
||||
mkfs.erofs -T0 --all-root --ignore-mtime -x-1 -Uclear \
|
||||
plain-nfs.erofs plain-src
|
||||
```
|
||||
|
||||
Fixture and manifest hashes:
|
||||
|
||||
| Object | SHA256 |
|
||||
|---|---|
|
||||
| `plain-nfs.erofs` | `6928f05b58ce462596f9c7bf1d3442ad50d555dc5c31ed01d28e1a921a448bf2` |
|
||||
| `metabox-nfs.erofs` | `8f493ed70d4be8096ab1adf8208622734d61a64903cf96a1ca1a1cc1854b1985` |
|
||||
| multidevice primary | `fa2f35f59f63cc4a56d230ba960a6c1d27748506fccce081447cab0dc0d540fd` |
|
||||
| multidevice blob 1 | `3aeb19c519636b61dc3ebd47c6286098deaa0990ff618ce3facbff4fd65907cd` |
|
||||
| source manifest | `0eda6f88c3b6c4b7c8d81d940a237d1f63c958a7f6fe7bdd4f77d6b293736fa9` |
|
||||
| `basic/regular.txt` | `93e7f327676f582356e9de2fcca7b714910227008fa1c5e7820e942c471edb6f` |
|
||||
| `basic/subdir/nested.txt` | `6da159d7ea19e9d1253ccb0f2c67586e6e075459954e740ff8405546683d87c3` |
|
||||
| `concurrent/file-000.dat` | `8435866622642b6dadf502b09f95af32cb01907ba3376e4f1fe96278e5ada692` |
|
||||
| `throughput.dat` (21,757,952 bytes) | `0841effed82d1adf394b6834ce30d4d9eb5fc9427527e854ec1cb6bb4b86c119` |
|
||||
| expected 12,050-name sorted list | `4fbf1b6103e8884223629c3fd03f4ec36061e65cd2a1becfc96670daa96f9d71` |
|
||||
|
||||
The metabox image was the previously qualified compressed-metabox fixture. The
|
||||
multidevice fixture was the previously qualified primary-plus-one-blob image.
|
||||
|
||||
## FreeBSD 15 setup commands
|
||||
|
||||
The artifacts were copied to the existing VM through SSH port 9222. Password
|
||||
material and askpass script contents were neither printed nor recorded.
|
||||
|
||||
```sh
|
||||
scp -P 9222 erofs.ko plain-nfs.erofs metabox-nfs.erofs \
|
||||
tests/nfs_fh_tool.c [email protected]:/tmp/repo22-nfs-test/
|
||||
ssh -p 9222 [email protected]
|
||||
cd /tmp/repo22-nfs-test
|
||||
sha256 erofs.ko plain-nfs.erofs metabox-nfs.erofs
|
||||
cc -O2 -Wall -Wextra -std=c17 nfs_fh_tool.c -o nfs_fh_tool
|
||||
```
|
||||
|
||||
The guest hashes matched the host hashes.
|
||||
|
||||
EROFS and NFS setup:
|
||||
|
||||
```sh
|
||||
kldload /tmp/repo22-nfs-test/erofs.ko
|
||||
mdconfig -a -t vnode -f /tmp/repo22-nfs-test/plain-nfs.erofs -u 42
|
||||
mkdir -p /mnt/repo22-erofs
|
||||
mount -t erofs -o ro /dev/md42 /mnt/repo22-erofs
|
||||
mount -v | grep /mnt/repo22-erofs
|
||||
|
||||
printf '%s\n' \
|
||||
'/mnt/repo22-erofs -ro -maproot=root -network 127.0.0.0 -mask 255.0.0.0' \
|
||||
> /etc/exports
|
||||
service rpcbind onestart
|
||||
service mountd onestart
|
||||
service nfsd onestart
|
||||
service mountd onereload
|
||||
rpcinfo -p 127.0.0.1
|
||||
showmount -e 127.0.0.1
|
||||
mount -v | grep /mnt/repo22-erofs
|
||||
```
|
||||
|
||||
Before mountd reload the EROFS line did not contain `NFS exported`. Afterwards:
|
||||
|
||||
```text
|
||||
/dev/md42 on /mnt/repo22-erofs (erofs, NFS exported, local, read-only, acls, ...)
|
||||
```
|
||||
|
||||
NFSv2/v3 and mountd were registered over TCP and UDP. NFS components are built
|
||||
into this GENERIC kernel: `kldload nfsd` and `kldload nfscl` reported
|
||||
`already loaded or in kernel`, while rpcbind/mountd/nfsd operated normally.
|
||||
|
||||
## File-handle validation commands
|
||||
|
||||
Normal vnode types:
|
||||
|
||||
```sh
|
||||
./nfs_fh_tool capture /mnt/repo22-erofs/basic/regular.txt regular.before.fh
|
||||
./nfs_fh_tool capture /mnt/repo22-erofs/basic/subdir directory.fh
|
||||
./nfs_fh_tool lcapture /mnt/repo22-erofs/basic/link-to-regular symlink.fh
|
||||
./nfs_fh_tool capture /mnt/repo22-erofs/basic/test.fifo fifo.fh
|
||||
./nfs_fh_tool describe regular.before.fh
|
||||
./nfs_fh_tool describe directory.fh
|
||||
./nfs_fh_tool describe symlink.fh
|
||||
./nfs_fh_tool describe fifo.fh
|
||||
./nfs_fh_tool stat regular.before.fh
|
||||
./nfs_fh_tool stat directory.fh
|
||||
./nfs_fh_tool stat symlink.fh
|
||||
./nfs_fh_tool stat fifo.fh
|
||||
./nfs_fh_tool cat regular.before.fh regular.fhopen.out
|
||||
```
|
||||
|
||||
Observed handles:
|
||||
|
||||
| Type | NID | Generation | Result |
|
||||
|---|---:|---:|---|
|
||||
| regular | `0x74` | 1 | `fhstat` and `fhopen` PASS |
|
||||
| directory | `0x76` | 1 | `fhstat` PASS |
|
||||
| symlink (`lgetfh`) | `0x72` | 1 | `fhstat` PASS |
|
||||
| FIFO | `0x79` | 1 | `fhstat` PASS |
|
||||
|
||||
All had `len=16` and `pad=0`. The regular full-handle SHA256 was:
|
||||
|
||||
```text
|
||||
fb1778fc6bdc979e10d003dd6131a961e7532baa16273c5465935a665bf990f6
|
||||
```
|
||||
|
||||
Malformed and stale classification:
|
||||
|
||||
```sh
|
||||
./nfs_fh_tool mutate regular.before.fh bad-len.fh len 15
|
||||
./nfs_fh_tool mutate regular.before.fh bad-pad.fh pad 1
|
||||
./nfs_fh_tool mutate regular.before.fh bad-gen.fh gen 2
|
||||
./nfs_fh_tool mutate regular.before.fh bad-nid.fh nid_hi 0xffffffff
|
||||
./nfs_fh_tool expect-stat bad-len.fh EINVAL
|
||||
./nfs_fh_tool expect-open bad-len.fh EINVAL
|
||||
./nfs_fh_tool expect-stat bad-pad.fh EINVAL
|
||||
./nfs_fh_tool expect-open bad-pad.fh EINVAL
|
||||
./nfs_fh_tool expect-stat bad-gen.fh ESTALE
|
||||
./nfs_fh_tool expect-open bad-gen.fh ESTALE
|
||||
./nfs_fh_tool expect-stat bad-nid.fh ESTALE
|
||||
./nfs_fh_tool expect-open bad-nid.fh ESTALE
|
||||
```
|
||||
|
||||
All eight checks passed. `fhstat` exercised the shared-lock `VFS_FHTOVP` path;
|
||||
`fhopen` exercised the exclusive-lock path.
|
||||
|
||||
Same explicit md-unit remount:
|
||||
|
||||
```sh
|
||||
umount /mnt/repo22-erofs
|
||||
mdconfig -d -u 42
|
||||
mdconfig -a -t vnode -f plain-nfs.erofs -u 42
|
||||
mount -t erofs -o ro /dev/md42 /mnt/repo22-erofs
|
||||
./nfs_fh_tool capture /mnt/repo22-erofs/basic/regular.txt regular.after.fh
|
||||
./nfs_fh_tool compare regular.before.fh regular.after.fh
|
||||
./nfs_fh_tool cat regular.before.fh regular.remount.out
|
||||
```
|
||||
|
||||
The complete handles were byte-identical and the pre-remount handle still read
|
||||
the correct file after remount.
|
||||
|
||||
Non-export mount update rejection:
|
||||
|
||||
```sh
|
||||
mount -u -o noexec /mnt/repo22-erofs
|
||||
```
|
||||
|
||||
Result: `Operation not supported`; the mount remained exported, local,
|
||||
read-only, and did not gain `noexec`.
|
||||
|
||||
## NFSv3 basic and restart tests
|
||||
|
||||
Four NFSv3 TCP READDIRPLUS clients were mounted:
|
||||
|
||||
```sh
|
||||
mount_nfs -o nfsv3,tcp,rdirplus,readdirsize=512 \
|
||||
127.0.0.1:/mnt/repo22-erofs /mnt/repo22-nfs-512
|
||||
mount_nfs -o nfsv3,tcp,rdirplus,readdirsize=1024 \
|
||||
127.0.0.1:/mnt/repo22-erofs /mnt/repo22-nfs-1024
|
||||
mount_nfs -o nfsv3,tcp,rdirplus,readdirsize=4096 \
|
||||
127.0.0.1:/mnt/repo22-erofs /mnt/repo22-nfs-4096
|
||||
mount_nfs -o nfsv3,tcp,rdirplus \
|
||||
127.0.0.1:/mnt/repo22-erofs /mnt/repo22-nfs-default
|
||||
nfsstat -m
|
||||
```
|
||||
|
||||
FreeBSD clamped the requested 512/1024/4096 values to an effective
|
||||
`readdirsize=8192`; the default client used 65536. This was recorded as a client
|
||||
environment limit, not a failure.
|
||||
|
||||
For each client:
|
||||
|
||||
```sh
|
||||
cmp NFS/basic/regular.txt DIRECT/basic/regular.txt
|
||||
test -d NFS/basic/subdir
|
||||
test "$(readlink NFS/basic/link-to-regular)" = regular.txt
|
||||
test -p NFS/basic/test.fifo
|
||||
test "$(stat -f %i NFS/basic/regular.txt)" = \
|
||||
"$(stat -f %i DIRECT/basic/regular.txt)"
|
||||
! touch NFS/write-must-fail
|
||||
```
|
||||
|
||||
All passed. Writes failed with `Read-only file system`.
|
||||
|
||||
nfsd restart with an open descriptor:
|
||||
|
||||
```sh
|
||||
exec 3< /mnt/repo22-nfs-default/basic/regular.txt
|
||||
service nfsd onerestart
|
||||
cat <&3 > open-fd-after-nfsd-restart.out
|
||||
exec 3<&-
|
||||
cmp open-fd-after-nfsd-restart.out /mnt/repo22-erofs/basic/regular.txt
|
||||
```
|
||||
|
||||
The open descriptor and subsequent path reopen both passed. Direct file handles
|
||||
captured before and after restart were byte-identical.
|
||||
|
||||
## Metabox bit-63 test
|
||||
|
||||
Commands:
|
||||
|
||||
```sh
|
||||
mdconfig -a -t vnode -f metabox-nfs.erofs -u 43
|
||||
mount -t erofs -o ro /dev/md43 /mnt/repo22-metabox
|
||||
./nfs_fh_tool capture /mnt/repo22-metabox/long/file1.txt metabox.before.fh
|
||||
./nfs_fh_tool describe metabox.before.fh
|
||||
./nfs_fh_tool stat metabox.before.fh
|
||||
./nfs_fh_tool cat metabox.before.fh metabox.fhopen.out
|
||||
./nfs_fh_tool mutate metabox.before.fh metabox.bad-nid.fh \
|
||||
nid_lo 0xffffffff
|
||||
./nfs_fh_tool expect-stat metabox.bad-nid.fh ESTALE
|
||||
./nfs_fh_tool expect-open metabox.bad-nid.fh ESTALE
|
||||
```
|
||||
|
||||
Observed handle:
|
||||
|
||||
```text
|
||||
len=16 pad=0 nid=800000000000010a gen=1
|
||||
ino=9223372036854776074
|
||||
```
|
||||
|
||||
The metabox mount was exported with mountd and mounted over NFSv3. Direct,
|
||||
`fhopen`, and NFS contents had SHA256:
|
||||
|
||||
```text
|
||||
661b22d2a7bd94a7da35834d4cc3647eb527d48eb08acc75bde2e3a7691ed45e
|
||||
```
|
||||
|
||||
The NFS and direct inode numbers matched. The out-of-range metabox mutation
|
||||
returned `ESTALE` through both shared and exclusive paths.
|
||||
|
||||
## Multidevice boundary test
|
||||
|
||||
Normal NFS commands:
|
||||
|
||||
```sh
|
||||
mdconfig -a -t vnode -f primary.img -u 44
|
||||
mdconfig -a -t vnode -f blob1.img -u 45
|
||||
mount -t erofs -o ro -o device.1=/dev/md45 /dev/md44 \
|
||||
/mnt/repo22-multidev
|
||||
mount_nfs -o nfsv3,tcp,rdirplus,readdirsize=512 \
|
||||
127.0.0.1:/mnt/repo22-multidev /mnt/repo22-nfs-multidev
|
||||
cmp /mnt/repo22-nfs-multidev/alpha.bin /mnt/repo22-multidev/alpha.bin
|
||||
cmp /mnt/repo22-nfs-multidev/small.txt /mnt/repo22-multidev/small.txt
|
||||
```
|
||||
|
||||
Normal multidevice NFS reads passed. Representative hashes:
|
||||
|
||||
```text
|
||||
f779dbe3aeaa609beb32212579f3aea441f244621d13c977cb8b52d7ec3016d9 alpha.bin
|
||||
5990b055a3c6d27681185a25e15ff21ba3cbdca7632faf4332233238e5f45aad small.txt
|
||||
001bcf4626e7d52f1bd76dee9a6c6e7d3a718c03602c5f9b07840a6c98b8fdc3 tree/beta.bin
|
||||
```
|
||||
|
||||
Cold detached-provider test:
|
||||
|
||||
```sh
|
||||
umount /mnt/repo22-multidev
|
||||
mdconfig -d -u 44
|
||||
mdconfig -a -t vnode -f primary.img -u 44
|
||||
mdconfig -a -t vnode -f blob1.img -u 45
|
||||
mount -t erofs -o ro -o device.1=/dev/md45 /dev/md44 \
|
||||
/mnt/repo22-multidev
|
||||
./nfs_fh_tool capture /mnt/repo22-multidev/tree/beta.bin multidev-beta.fh
|
||||
./nfs_fh_tool stat multidev-beta.fh
|
||||
mdconfig -d -o force -u 45
|
||||
! ./nfs_fh_tool cat multidev-beta.fh beta.after-orphan
|
||||
```
|
||||
|
||||
The handle resolved to NID `0x36`, then the cold read returned:
|
||||
|
||||
```text
|
||||
Device not configured
|
||||
```
|
||||
|
||||
This preserved the multidevice `ENXIO` boundary error instead of converting it
|
||||
to `ESTALE`, reading past the provider, or panicking.
|
||||
|
||||
## READDIRPLUS and stress
|
||||
|
||||
Every one of the four initial listings returned exactly 12,050 unique expected
|
||||
names. The expected list and all four sorted outputs had SHA256:
|
||||
|
||||
```text
|
||||
4fbf1b6103e8884223629c3fd03f4ec36061e65cd2a1becfc96670daa96f9d71
|
||||
```
|
||||
|
||||
Dot handling:
|
||||
|
||||
```sh
|
||||
ls -a1 /mnt/repo22-nfs-default/bigdir > nfs-dot-list
|
||||
test "$(wc -l < nfs-dot-list)" -eq 12052
|
||||
test "$(grep -cx '\.' nfs-dot-list)" -eq 1
|
||||
test "$(grep -cx '\.\.' nfs-dot-list)" -eq 1
|
||||
```
|
||||
|
||||
All passed.
|
||||
|
||||
Cold pagination used five unmount/remount/list/compare cycles on the small
|
||||
client. Concurrency then used eight workers, three complete 12,050-entry rounds
|
||||
each, distributed across the four mounts. All 29 cold/concurrent sorted outputs
|
||||
had one unique SHA256 value, matching the expected list.
|
||||
|
||||
Parallel data/metadata load:
|
||||
|
||||
```sh
|
||||
for M in 512 1024 4096 default; do
|
||||
find /mnt/repo22-nfs-$M/concurrent -type f -maxdepth 1 -print0 | \
|
||||
xargs -0 -n 1 -P 8 cat > /dev/null &
|
||||
find /mnt/repo22-nfs-$M/concurrent -type f -maxdepth 1 -print0 | \
|
||||
xargs -0 -n 1 -P 8 stat -f '%i %z' > /dev/null &
|
||||
done
|
||||
wait
|
||||
```
|
||||
|
||||
All jobs completed successfully.
|
||||
|
||||
Final stress counters:
|
||||
|
||||
| Counter | Client | Server |
|
||||
|---|---:|---:|
|
||||
| requests / cache misses | 270,854 | 271,174 |
|
||||
| lookup | 264,989 | 265,109 |
|
||||
| read | 1,360 | 1,360 |
|
||||
| READDIRPLUS | 2,512 | 2,512 |
|
||||
| write | 0 | 0 |
|
||||
| timed out | 0 | n/a |
|
||||
| retries | 0 | n/a |
|
||||
|
||||
There were no READDIR RPCs because the clients negotiated READDIRPLUS.
|
||||
|
||||
Informational throughput:
|
||||
|
||||
```text
|
||||
21757952 bytes transferred in 5.843937 seconds
|
||||
3723167 bytes/second
|
||||
```
|
||||
|
||||
No fixed throughput threshold was used.
|
||||
|
||||
## Test matrix
|
||||
|
||||
| Test | Actual | Status |
|
||||
|---|---|---|
|
||||
| isolated `./build.sh` | success; module hash recorded | PASS |
|
||||
| unresolved `bcmp` | none | PASS |
|
||||
| FreeBSD helper compile | `-Wall -Wextra`, no warnings | PASS |
|
||||
| final `kldload` / `kldunload` | load, unload, reload, unload succeeded | PASS |
|
||||
| initial mount export flag | absent before mountd | PASS |
|
||||
| export-only `MNT_UPDATE` | mountd installed export | PASS |
|
||||
| non-export `MNT_UPDATE` | `EOPNOTSUPP`, flags unchanged | PASS |
|
||||
| regular file | direct, `fhopen`, NFS content match | PASS |
|
||||
| directory | handle and NFS traversal | PASS |
|
||||
| symlink | `lgetfh`, target and NFS lookup | PASS |
|
||||
| FIFO | handle metadata and NFS type | PASS |
|
||||
| read-only behavior | direct and all NFS mounts rejected writes | PASS |
|
||||
| 16-byte handle ABI | len/pad/NID hi/NID lo/gen observed | PASS |
|
||||
| generation | `1`, matching `va_gen` | PASS |
|
||||
| shared/exclusive lock contract | `fhstat` and `fhopen` passed | PASS |
|
||||
| malformed len/pad | `EINVAL` | PASS |
|
||||
| stale generation/NID | `ESTALE` | PASS |
|
||||
| same `md42` remount | complete handle byte-identical | PASS |
|
||||
| nfsd restart | open descriptor and reopen passed | PASS |
|
||||
| metabox bit 63 | real `0x800000000000010a` round-trip | PASS |
|
||||
| metabox boundary mutation | `ESTALE` | PASS |
|
||||
| multidevice NFS | external-data reads matched | PASS |
|
||||
| detached external device | cold handle read preserved `ENXIO` | PASS |
|
||||
| 12,050-entry listings | exact count and names on four clients | PASS |
|
||||
| cookie pagination | five cold remounts, no duplicate/omission | PASS |
|
||||
| dot omitted | one `.` and one `..`, 12,052 total | PASS |
|
||||
| concurrent traversal | 8 workers x 3 rounds, all hashes equal | PASS |
|
||||
| concurrent read/stat | all jobs completed | PASS |
|
||||
| READDIRPLUS | client/server 2,512 | PASS |
|
||||
| NFS timeouts/retries | 0 / 0 | PASS |
|
||||
| successful NFS writes | 0 | PASS |
|
||||
| throughput | 3,723,167 B/s, informational | INFO |
|
||||
|
||||
## dmesg and cleanup
|
||||
|
||||
The functional matrix captured dmesg before loading the test module and after
|
||||
all functional tests plus a load/unload cycle. Both files contained the same
|
||||
two pre-existing historical lines and compared byte-for-byte equal:
|
||||
|
||||
```text
|
||||
interface erofs.1 already present in the KLD 'erofs-repo22-xattrfix.ko'!
|
||||
md0: truncating fractional last sector by 14 bytes
|
||||
```
|
||||
|
||||
No EROFS error, stale-handle message, trap, panic, or new kernel line occurred
|
||||
during the functional matrix.
|
||||
|
||||
The first cleanup attempt used the wrong order: it stopped nfsd and unmounted
|
||||
the direct EROFS mounts before one hard loopback client was fully gone. This
|
||||
left a `mount -p` process in uninterruptible NFS wait. When rpcbind/nfsd were
|
||||
temporarily restarted without the underlying EROFS export to release those
|
||||
clients, the NFS client logged five `fileid changed` messages. These messages
|
||||
occurred after the byte-identical functional dmesg snapshot and are classified
|
||||
as **environment cleanup artifact**, not an EROFS functional failure.
|
||||
|
||||
The VM was rebooted to remove the blocked cleanup process. Post-reboot audit:
|
||||
|
||||
```text
|
||||
erofs_mounts=0
|
||||
nfs_mounts=0
|
||||
md_units=0
|
||||
erofs_modules=0
|
||||
nfsd_pids=0 mountd_pids=0 rpcbind_pids=0
|
||||
exports_exists=no
|
||||
```
|
||||
|
||||
A final correctly ordered clean rerun then performed:
|
||||
|
||||
```sh
|
||||
kldload erofs.ko
|
||||
mdconfig -a -t vnode -f plain-nfs.erofs -u 42
|
||||
mount -t erofs -o ro /dev/md42 /mnt/repo22-erofs
|
||||
service rpcbind onestart
|
||||
service mountd onestart
|
||||
service nfsd onestart
|
||||
mount_nfs -o nfsv3,tcp,rdirplus,readdirsize=512 \
|
||||
127.0.0.1:/mnt/repo22-erofs /mnt/repo22-nfs-clean
|
||||
cmp /mnt/repo22-nfs-clean/basic/regular.txt \
|
||||
/mnt/repo22-erofs/basic/regular.txt
|
||||
test "$(find /mnt/repo22-nfs-clean/bigdir -type f -maxdepth 1 | wc -l)" \
|
||||
-eq 12050
|
||||
umount /mnt/repo22-nfs-clean
|
||||
: > /etc/exports
|
||||
service mountd onereload
|
||||
service nfsd onestop
|
||||
service mountd onestop
|
||||
service rpcbind onestop
|
||||
umount /mnt/repo22-erofs
|
||||
mdconfig -d -u 42
|
||||
kldunload erofs
|
||||
```
|
||||
|
||||
The clean rerun's pre/post dmesg files compared byte-for-byte equal. Final
|
||||
state after that rerun:
|
||||
|
||||
```text
|
||||
clean_rerun_erofs=0 nfs=0 md=0 module=0 services=0 exports=absent
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- **ENVIRONMENT-LIMIT**: FreeBSD clamped requested readdir sizes 512, 1024, and
|
||||
4096 to an effective minimum of 8192. The test still exercised a much smaller
|
||||
value than the 65536 default and produced 2,512 real READDIRPLUS calls.
|
||||
- **ENVIRONMENT-CLEANUP-ARTIFACT**: the five post-test `fileid changed` lines
|
||||
were caused by the explicitly documented wrong cleanup order. A reboot and a
|
||||
correctly ordered rerun left no new dmesg lines or resources.
|
||||
- **KERNEL-FAIL**: none.
|
||||
- **NOT RUN**: none of the required functional cases were omitted.
|
||||
@@ -0,0 +1,142 @@
|
||||
# repo22 multi-device review findings manual test report
|
||||
|
||||
Date: 2026-08-08 21:14 UTC
|
||||
Baseline: `2354b4487c84458c1e447f4d0bb7b55daebcca26` plus this review fix
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
Linux reference: 7.1-rc1 `/work/dev-src-linux/fs/erofs`
|
||||
Host tools: erofs-utils 1.8.6
|
||||
|
||||
## Result
|
||||
|
||||
**PASS**. All five multi-device review findings were fixed and exercised with
|
||||
deterministic positive and negative fixtures. A real two-block LZ4 pcluster
|
||||
was read from an external provider and the complete 1 MiB output matched the
|
||||
source SHA256. Multi-device NFS reads and forced-device-removal behavior also
|
||||
passed.
|
||||
|
||||
## Source changes validated
|
||||
|
||||
- Non-zero device IDs accept a slot with `uniaddr=0`; unified device-ID-0
|
||||
lookup skips such slots.
|
||||
- `devt_slotoff=0` reads the table from byte offset zero, as Linux 7.1 does.
|
||||
- Device mapping carries the requested extent length. Flatdev reads must fit
|
||||
wholly in the primary range or one non-zero unified slot; gaps, adjacent-slot
|
||||
crossings, and pcluster crossings fail closed.
|
||||
- Declared image and slot bounds are checked before physical media size, so
|
||||
format corruption is `EINTEGRITY` and a genuinely short provider is
|
||||
`ENXIO`.
|
||||
- Primary and external `namei()` failures preserve `ENOENT`; omitted required
|
||||
`device.N` options still return `ENXIO`.
|
||||
|
||||
The NFS file-handle ABI, `VFS_FHTOVP`, inode bounds, readdir cookies, and GEOM
|
||||
orphan lifecycle from `2354b448` were not changed.
|
||||
|
||||
## Build
|
||||
|
||||
Commands:
|
||||
|
||||
```sh
|
||||
git diff --check
|
||||
./build.sh
|
||||
nm -u build/erofs.ko | awk '$NF == "bcmp" {bad=1} END {exit bad}'
|
||||
sha256sum build/erofs.ko
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- Cross-build: PASS.
|
||||
- `git diff --check`: PASS.
|
||||
- Unresolved `bcmp`: none.
|
||||
- Module SHA256:
|
||||
`b85c429fa73e6cebc10e6e3941575a6ca170306ab4fa9ccca4006c922971acb8`.
|
||||
- FreeBSD `kldload` and `kldunload`: PASS.
|
||||
|
||||
## Fixture construction
|
||||
|
||||
The chunk fixtures derive from the checksum-valid TC094 images recorded in the
|
||||
19:37 multi-device report. The patcher asserted the original table values
|
||||
`slot1=(blocks=19, uniaddr=2)`, `slot2=(blocks=25, uniaddr=21)`, and the first
|
||||
32 KiB chunk index `(device_id=2, startblk=9)` before each change.
|
||||
|
||||
The compressed baseline was generated from the deterministic `shape.dat`:
|
||||
|
||||
```sh
|
||||
mkfs.erofs -T0 -U00000000-0000-0000-0000-000000000000 \
|
||||
--all-root -E legacy-compress -zlz4 -C65536 \
|
||||
lz4-full-64k.erofs src-big64k
|
||||
dump.erofs --path=/shape.dat -e lz4-full-64k.erofs
|
||||
```
|
||||
|
||||
The baseline contains one 1 MiB logical extent backed by an 8192-byte physical
|
||||
pcluster. Its HEAD pblk was changed from block 1 to unified block 4; the two
|
||||
compressed blocks were copied to a two-block external provider and zeroed in
|
||||
the primary image. A checksum-valid device table declared
|
||||
`slot1=(blocks=2, uniaddr=4)`.
|
||||
|
||||
For the crossing negative, the same pcluster was placed across adjacent
|
||||
`slot1=[4,5)` and `slot2=[5,6)`. The flatdev contains both real compressed
|
||||
blocks, proving that failure is caused by the declared slot boundary rather
|
||||
than absent data.
|
||||
|
||||
erofs-utils 1.8.6 parses the external compressed extent but its userspace
|
||||
compressed read path does not propagate the unified slot ID to `erofs_dev_read`;
|
||||
therefore userspace extraction of this layout is tool-unavailable. The
|
||||
FreeBSD kernel full-file SHA256 is the authoritative read validation.
|
||||
|
||||
## Fixture hashes
|
||||
|
||||
| Fixture | SHA256 |
|
||||
|---|---|
|
||||
| zero-uniaddr primary | `d38935291fb2ee756e0c192d1b0d39b4f4f5b4047aa37c2de44cad91a55fa018` |
|
||||
| zero-uniaddr device-ID-0 control | `fac9db7631cd06d17212cbe3e7b1d681766f031d1e41e443f71f1bad39f9b960` |
|
||||
| slotoff-zero primary | `39e3e8c60bea962f1200550316be87ec7da7b15c0ba11c6e5edf05f0715db451` |
|
||||
| cross-slot primary | `edd0cba92a26bc9a7a59561732fb8e045b2e1565d18d9f9b3143f86f64649dee` |
|
||||
| cross-slot flatdev | `6e2834450081d778cee37fa3e050249f04927def205d6816aab814a2912744bc` |
|
||||
| gap flatdev | `5bc07b66f7b0f83e9568ba781dcc78a08d45e617dc27bb365ed3ee2c12353218` |
|
||||
| external-compressed primary | `5625612426d68624c77dd94b09754da583fc6bd90767e9e9d958e4fc89131a4d` |
|
||||
| external-compressed blob | `0d71102ce471af0a30d4d61a1fbeef1fc33437c85651d7c7f9c60ebd305ec9d0` |
|
||||
| cross-pcluster primary | `2bce4a91c1aedc4bab2e65582ef9cb465094f825a051c0203d52b3d18bb17ed8` |
|
||||
| cross-pcluster flatdev | `bc07fb06ca5c6bb60ef1a989eecbd3d714450092ba756f85f5b060c8b8b2ec3a` |
|
||||
|
||||
Source `shape.dat` SHA256:
|
||||
`370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52`.
|
||||
|
||||
## FreeBSD result matrix
|
||||
|
||||
| Test | Actual | Status |
|
||||
|---|---|---|
|
||||
| slot 1 `uniaddr=0`, explicit device ID 1 | complete striped and second-file SHA256 matched | PASS |
|
||||
| slot 1 `uniaddr=0`, device ID 0 control | slot skipped; primary-bound `EINTEGRITY` | PASS |
|
||||
| `devt_slotoff=0` | mount and complete striped SHA256 matched | PASS |
|
||||
| nonexistent primary path | `ENOENT` / `No such file or directory` | PASS |
|
||||
| nonexistent external path | `ENOENT` / `No such file or directory` | PASS |
|
||||
| omitted slot option | `ENXIO` / `Device not configured` | PASS |
|
||||
| flatdev chunk crosses adjacent slots | read returned `EINTEGRITY` | PASS |
|
||||
| device-ID-0 chunk starts in a gap | read returned `EINTEGRITY` | PASS |
|
||||
| explicit chunk exceeds declared slot and media | `EINTEGRITY`, not `ENXIO` | PASS |
|
||||
| real external 64 KiB LZ4 pcluster | complete 1 MiB SHA256 matched | PASS |
|
||||
| flatdev pcluster crosses adjacent slots | read returned `EINTEGRITY` | PASS |
|
||||
| explicit pcluster crosses two providers | read returned `EINTEGRITY` | PASS |
|
||||
|
||||
## NFS smoke
|
||||
|
||||
The external-compressed image was exported read-only over local NFSv3/TCP.
|
||||
The NFS client read the complete `shape.dat`; SHA256 matched the source.
|
||||
|
||||
The zero-uniaddr two-slot image was then exported. The client obtained stable
|
||||
inode and size data for a cold slot-2 file, slot 2 was forcibly orphaned, and
|
||||
the same NFS pathname still resolved to identical metadata. Its first cold data
|
||||
read failed with the underlying device error. This preserves the `2354b448`
|
||||
contract: file-handle resolution is not misreported as `ESTALE`, while missing
|
||||
external data remains an I/O/device failure.
|
||||
|
||||
## Cleanup
|
||||
|
||||
All EROFS and NFS mounts were unmounted, NFS services stopped, `/etc/exports`
|
||||
cleared, md providers detached, and the module unloaded. No image, overlay,
|
||||
fixture, or build artifact is part of this commit.
|
||||
|
||||
A final representative rerun covered the external-compressed positive, the
|
||||
flatdev-gap negative, and the offset-zero table positive. The dmesg SHA256 was
|
||||
unchanged before and after:
|
||||
`897003051a6a50875d49715bfa27907611cedac9e42e0a06ca7377105ac695ae`.
|
||||
@@ -0,0 +1,406 @@
|
||||
# repo22 compression P0 WIP integration manual report
|
||||
|
||||
Date: 2026-08-08 23:06 UTC
|
||||
Baseline: `78182686e968c659932458cbe7a1e0889397f20b`
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
Build reference: `/work/dev-freebsd-releng` (`REVISION=15.0`, source branch
|
||||
`RELEASE-p9`)
|
||||
Linux behavior reference: `/work/dev-src-linux/fs/erofs`
|
||||
Host production tool: erofs-utils 1.8.6
|
||||
|
||||
## Result
|
||||
|
||||
**PASS**, with the extent-record guest case accurately recorded as
|
||||
**MKFS-UNAVAILABLE** rather than PASS.
|
||||
|
||||
The retained ten-file WIP was reviewed and integrated without reset, checkout,
|
||||
stash, or revert. Both module configurations build and load. DEFLATE, ZSTD,
|
||||
and MicroLZMA full and partial references read correctly; targeted corruption
|
||||
returns `EIO`. A fragment-backed compressed metabox now works after packed
|
||||
inode initialization, while loop, recursive-NID, and range mutations fail
|
||||
closed. HEAD2 and interlaced images pass real FreeBSD kernel reads. LZ4,
|
||||
xattr/metabox, chunk, external compressed multi-device, and NFS regressions
|
||||
also pass.
|
||||
|
||||
No CI or test runner was added. Only source, Markdown manual tests, and this
|
||||
report are intended for the commit.
|
||||
|
||||
## Instruction and baseline audit
|
||||
|
||||
- `find /work -name AGENTS.md -type f -print` returned no paths. There were no
|
||||
applicable `AGENTS.md` files.
|
||||
- Initial local `HEAD` and `xdm/main` both resolved to
|
||||
`78182686e968c659932458cbe7a1e0889397f20b`.
|
||||
- The exact ten tracked WIP files were present:
|
||||
`build.sh`, `src/Makefile`, `src/decompressor.c`, `src/deflate.c`,
|
||||
`src/internal.h`, `src/lzma.c`, `src/super.c`, `src/zdata.c`, `src/zmap.c`,
|
||||
and `src/zstd.c`.
|
||||
- Untracked build, fixture, overlay, artifact, and other-repository paths were
|
||||
not staged.
|
||||
|
||||
## Initial guest module cleanup
|
||||
|
||||
Before any build under test was loaded, the guest reported:
|
||||
|
||||
```text
|
||||
Id Refs Address Size Name
|
||||
5 1 0xffffffff82822000 a690 erofs-nozstd.ko
|
||||
```
|
||||
|
||||
`kldstat -v -i 5` proved the path was `./erofs-nozstd.ko` and the contained
|
||||
module name was `erofs`. The cleanup used the observed KLD ID, not a guessed
|
||||
filename:
|
||||
|
||||
```sh
|
||||
kldunload -i 5
|
||||
kldstat
|
||||
kldstat | grep -i erofs
|
||||
```
|
||||
|
||||
The second `kldstat` contained no EROFS entry and the final grep printed
|
||||
`none`.
|
||||
|
||||
## Source review
|
||||
|
||||
### Packed inode and metabox order
|
||||
|
||||
- `packed_nid` and `metabox_nid` are decoded and checked before carrier loads.
|
||||
- A packed NID with the metabox selector bit is rejected before any metadata
|
||||
recursion can begin.
|
||||
- The packed inode is loaded before the metabox inode, matching the Linux
|
||||
dependency order and allowing a compressed metabox to terminate in a packed
|
||||
fragment.
|
||||
- The packed inode must be a regular, non-fragment inode. This rejects a packed
|
||||
carrier that would recurse back through itself.
|
||||
- A fragment-backed metabox must have a distinct loaded packed inode, non-zero
|
||||
size, a real fragment tail mapping, and a range wholly inside the packed
|
||||
inode.
|
||||
- Fragment recursion checks compare inode NIDs rather than object pointers,
|
||||
which also catches separately allocated `erofs_node` objects for the same
|
||||
on-disk inode.
|
||||
|
||||
### DEFLATE, ZSTD, and MicroLZMA
|
||||
|
||||
- DEFLATE uses `inflate(..., Z_SYNC_FLUSH)` until the requested output is full,
|
||||
detects no-progress loops, accepts `Z_OK` for partial output, and requires
|
||||
`Z_STREAM_END` plus complete input consumption for full output.
|
||||
- ZSTD uses FreeBSD's formal `<contrib/zstd/lib/zstd.h>` API with
|
||||
`ZSTD_createDCtx_advanced`, `ZSTD_DCtx_setParameter`,
|
||||
`ZSTD_decompressStream`, `ZSTD_isError`, and `ZSTD_freeDCtx`.
|
||||
- ZSTD partial decoding stops after the requested output is produced. Full
|
||||
decoding requires frame completion and complete input consumption.
|
||||
- MicroLZMA partial decoding accepts `XZ_OK` or `XZ_STREAM_END` after exact
|
||||
requested output. Full decoding requires `XZ_STREAM_END` and
|
||||
`buffer.in_pos == srclen`.
|
||||
- All decoder failures are translated to the filesystem read error `EIO` and
|
||||
all allocated decoder/output buffers are released.
|
||||
|
||||
### ZSTDIO build gate
|
||||
|
||||
- `src/Makefile` consumes `opt_zstdio.h` and adds FreeBSD's zstd compatibility
|
||||
include directory for `zstd.c`.
|
||||
- `build.sh` accepts only `EROFS_ZSTDIO=0` or `1` and creates the corresponding
|
||||
option header.
|
||||
- The disabled translation unit contains only the availability result and a
|
||||
local stub; it references no `ZSTD_*` symbol.
|
||||
- A filesystem advertising ZSTD is rejected during compression-config parsing
|
||||
when the module lacks ZSTDIO, before any file read can reach the stub.
|
||||
|
||||
### HEAD2, interlaced, and extent records
|
||||
|
||||
- HEAD2 selects `z_algorithmtype[1]` and respects the HEAD2 big-pcluster bit.
|
||||
- Plain records with interlaced advise use the interlaced byte rotation before
|
||||
returning data.
|
||||
- Extent records retain Linux ordering and responsibilities for 4-, 8-, 16-,
|
||||
and 32-byte records, partial references, explicit algorithm format, shifted
|
||||
and interlaced plain data, and final fragments.
|
||||
- Explicit extent tables with a non-empty file and zero extent count fail
|
||||
closed.
|
||||
- Final fragment mappings are checked against the loaded packed inode before
|
||||
reads.
|
||||
|
||||
## Build matrix
|
||||
|
||||
Host commands:
|
||||
|
||||
```sh
|
||||
EROFS_ZSTDIO=0 ./build.sh
|
||||
cp build/erofs.ko /tmp/repo22-erofs-nozstd-probe.ko
|
||||
nm -u /tmp/repo22-erofs-nozstd-probe.ko
|
||||
|
||||
EROFS_ZSTDIO=1 ./build.sh
|
||||
cp build/erofs.ko /tmp/repo22-erofs-zstd-probe.ko
|
||||
nm -u /tmp/repo22-erofs-zstd-probe.ko
|
||||
```
|
||||
|
||||
| Build | Size | SHA256 | `ZSTD_*` undefined | `bcmp` undefined | Guest KLD |
|
||||
|---|---:|---|---|---|---|
|
||||
| no ZSTDIO | 68744 | `c96f4e1620f005a19d7ed581f98f1894969b38a34d7021f54dbce99919888ef9` | none | none | load/unload PASS |
|
||||
| ZSTDIO | 69984 | `7f8cb03afb9c76b535709af1a06bf993cbab74bb5899b2f4646a6fd20b00d15c` | five formal API names | none | load/unload PASS |
|
||||
|
||||
The enabled module's unresolved ZSTD set was exactly:
|
||||
|
||||
```text
|
||||
ZSTD_DCtx_setParameter
|
||||
ZSTD_createDCtx_advanced
|
||||
ZSTD_decompressStream
|
||||
ZSTD_freeDCtx
|
||||
ZSTD_isError
|
||||
```
|
||||
|
||||
Both were loaded by path, identified with `kldstat -v -i ID`, and unloaded by
|
||||
that exact ID.
|
||||
|
||||
## ZSTDIO behavior
|
||||
|
||||
With the disabled module, a normal LZ4 image still read successfully:
|
||||
|
||||
```text
|
||||
nozstd_lz4_sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
|
||||
nozstd_lz4_dmesg_delta=empty
|
||||
```
|
||||
|
||||
The same module rejected the ZSTD image:
|
||||
|
||||
```text
|
||||
nozstd_mount_rc=1
|
||||
mount: /dev/md0: erofs: ZSTD compression requires ZSTDIO support: Operation not supported
|
||||
```
|
||||
|
||||
The ZSTDIO module mounted the image and produced the expected complete SHA256
|
||||
shown below.
|
||||
|
||||
## Fixture construction and proof
|
||||
|
||||
Fixtures and build outputs remained outside the repository commit.
|
||||
Authentication used the existing private askpass workflow; its contents and
|
||||
credentials were not printed or recorded.
|
||||
|
||||
### Partial-reference images
|
||||
|
||||
`dump.erofs -s` and `dump.erofs --path=PATH -e` established compressor,
|
||||
layout, logical lengths, physical starts, and pcluster lengths. Byte-level
|
||||
patching asserted the original inode NID, map-header offset, HEAD record, and
|
||||
pblk before setting `Z_EROFS_LI_PARTIAL_REF` or redirecting a reused pblk.
|
||||
CRC32C was recomputed for every superblock mutation.
|
||||
|
||||
| Image | SHA256 |
|
||||
|---|---|
|
||||
| `deflate-partial-ref.erofs` | `8bc720a1250ef28794d091b6264e76060fbf01d66ea706c59c24c1960fa347ac` |
|
||||
| `deflate-partial-ref-corrupt.erofs` | `6ff9dff9b3b5aba7da9b4a93f5b068270b4916267d2fe0fd3702c02be4c19fae` |
|
||||
| `lzma-partial-ref.erofs` | `2f4bd89d2340273dd4052ea73aa9ac43802db431258c118c5dfede7727be9408` |
|
||||
| `lzma-partial-ref-corrupt.erofs` | `bfbbd304a401c091d63e8b8e42b2b1984d9750760e940aded89acc909551d838` |
|
||||
| `zstd-partial-ref.erofs` | `7f5ee4f8a20f20d32eaf8780a7081229f7dbf6b896ad23986da36e1a1c580519` |
|
||||
| `zstd-partial-ref-corrupt.erofs` | `0c6e6b312b879297e2f71f406dcd35236654800e7cd115df0cb9a748297c48c1` |
|
||||
|
||||
The LZMA and ZSTD full file was 1048576 bytes. Their partial file was 700000
|
||||
bytes and reused the complete source pcluster. The DEFLATE fixture contained
|
||||
multiple real compressed extents, including reused physical extents in the
|
||||
partial file.
|
||||
|
||||
### Fragment-backed compressed metabox
|
||||
|
||||
A METABOX-capable inspector proved:
|
||||
|
||||
```text
|
||||
packed_nid=40
|
||||
metabox_nid=38
|
||||
metabox inode: regular, compressed-full, size=20480
|
||||
metabox fragment header=0x800000000004e800
|
||||
fragmentoff=321536
|
||||
packed inode: regular, plain, size=342016
|
||||
321536 + 20480 = 342016
|
||||
```
|
||||
|
||||
Positive image SHA256:
|
||||
`8a9a62bd203994711b8272192915d811e6c3de23e07ad9607dd63e66cc109bcd`.
|
||||
|
||||
The negative images changed one proven field each:
|
||||
|
||||
| Negative | Exact mutation | SHA256 |
|
||||
|---|---|---|
|
||||
| self-loop | `packed_nid: 40 -> 38` | `e9501ed9d149e2d735d95156669d144e733bf4be620bb69ea8a0c41f996b436c` |
|
||||
| range | `fragmentoff: 321536 -> 342016` | `3fdcf2a41f50da93a5931edcef0d86ff2f576ecba781833036eaa930d99197d5` |
|
||||
| metabox recursion | set bit 63 in `metabox_nid` | `b873e0cf4d2892d2d154a5769f494660f47f4429161e7be001b833da6dd0d705` |
|
||||
| packed recursion | set bit 63 in `packed_nid` | `4fa368d1b1d47bb56b82132e6055d105ed2508b179a8df2a98ec5a728f91c9ac` |
|
||||
|
||||
### HEAD2
|
||||
|
||||
The HEAD2 fixture patch asserted the original bytes and made only these semantic
|
||||
changes, plus the resulting CRC32C bytes:
|
||||
|
||||
```text
|
||||
feature_incompat: 0x00000003 -> 0x0000000b
|
||||
map h_advise: 0x0002 -> 0x0006
|
||||
first di_advise: HEAD1 (1) -> HEAD2 (3)
|
||||
```
|
||||
|
||||
Image SHA256:
|
||||
`fc70cbef0442a86ac2f507aebd7ac7bcfbdfcc3d0b9b3cf2ff5231334583d816`.
|
||||
The targeted corrupted copy SHA256 was
|
||||
`f0d9ad645804565c5aca7a92128df3ae9c81717a08ad7e8692489cd2ae606700`.
|
||||
|
||||
### Interlaced
|
||||
|
||||
This image was generated specifically with installed erofs-utils 1.8.6:
|
||||
|
||||
```sh
|
||||
mkfs.erofs -zlz4 -C4096 -Efragments -T0 \
|
||||
interlaced-1.8.6.erofs source
|
||||
```
|
||||
|
||||
`dump.erofs -e` reported 105 real extents, with 4096-byte plain extents
|
||||
interspersed with compressed extents. Image SHA256:
|
||||
`d77d86f874361bae86cae9e8f6d05d9b6c68ef04cd5c8779368aef033fa485c3`.
|
||||
|
||||
### Extent metadata
|
||||
|
||||
Installed `mkfs.erofs -V` reported 1.8.6. Exact source-tree search found no
|
||||
on-disk `Z_EROFS_ADVISE_EXTENTS`, `z_erofs_extent_recsize`, or
|
||||
`struct z_erofs_extent {` definition in the 1.8.6 include/lib tree. Its
|
||||
internal `struct z_erofs_extent_item` is an in-memory compressor item, not the
|
||||
new on-disk extent-record ABI.
|
||||
|
||||
Although a newer-tool extent image existed in the WIP build area, it was not
|
||||
mounted or scored. The required result is therefore:
|
||||
|
||||
```text
|
||||
extent metadata guest result: MKFS-UNAVAILABLE
|
||||
```
|
||||
|
||||
The independent static review compared `src/erofs_fs.h` and `src/zmap.c`
|
||||
against `/work/dev-src-linux/fs/erofs/erofs_fs.h` and `zmap.c`, covering record
|
||||
sizes 4/8/16/32, implicit physical bases, explicit-count binary search,
|
||||
physical/logical high words, format bits, partial references, interlaced data,
|
||||
fragments, metabox metadata reads, and malformed explicit zero counts.
|
||||
|
||||
## Core FreeBSD result matrix
|
||||
|
||||
The guest test used repeated `mdconfig -a -t vnode -f IMAGE`, read-only EROFS
|
||||
mounts, `sha256 -q`, byte-exact `dd`/`cmp`, and a small C helper that printed
|
||||
`errno` on read failure.
|
||||
|
||||
| Case | Complete SHA256 / result | Boundary or random proof | Status |
|
||||
|---|---|---|---|
|
||||
| DEFLATE full | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | full stream completion | PASS |
|
||||
| DEFLATE partial | `61b17076c2dfae88da7912d00df534b894cf6d27178863c6d8e91f8617ebb91e` | offset 122900, 512 bytes: `fa381301af1b62fa259addbe7ae427fd54486abc7604ea7619e7a9c47965606d`; offset 736700, 1024 bytes: `be1d2941b054626376fa58155ce0ef8d6357dd0defcbf9eaa37d19ff6098873c` | PASS |
|
||||
| DEFLATE corrupt | `read_errno=5`, failure after 65536 output bytes | target extent only | PASS |
|
||||
| MicroLZMA full | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | full input consumed | PASS |
|
||||
| MicroLZMA partial | `5a840803f5372b7be1db70713fed7705bf6e982ca2fd319f722c21f094f6c8dd` | offset 65500, 2048 bytes: `49c231f92dde0b0104d7e5b3a01d918dde818c3a6dca05393e2a424d01c214f2`; offset 524287, 4097 bytes: `b8e80c144eacd1f8863c72eab66272379923f0d738f63576ab8286455e0dde8c` | PASS |
|
||||
| MicroLZMA corrupt | `read_errno=5`, zero output bytes | target pcluster only | PASS |
|
||||
| ZSTD full | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | formal FreeBSD API | PASS |
|
||||
| ZSTD partial | `5a840803f5372b7be1db70713fed7705bf6e982ca2fd319f722c21f094f6c8dd` | same two boundary/random hashes as MicroLZMA | PASS |
|
||||
| ZSTD corrupt | `read_errno=5`, zero output bytes | target pcluster only | PASS |
|
||||
| HEAD2 full | `7e2f40362554f4460e80ec2f8d91f4e6c04a8e58f2980d637b2d383a7aa3b3f8` | offset 65500, 4096 bytes: `f0a80e3217f54897eae271b6e570c862abcffad3a4ffd19ffec0444ee06ae721` | PASS |
|
||||
| HEAD2 corrupt | `read_errno=5`, zero output bytes | patched HEAD2 pcluster | PASS |
|
||||
| interlaced full | `7e2f40362554f4460e80ec2f8d91f4e6c04a8e58f2980d637b2d383a7aa3b3f8` | offset 16240, 8192 bytes across first compressed/plain transition: `b1387900e55e5672944f8299fe66ac9542007f2b0cbed81d5974831d6040cdae` | PASS |
|
||||
| extent records | static format/control-flow review only | erofs-utils 1.8.6 cannot emit | MKFS-UNAVAILABLE |
|
||||
|
||||
## Fragment-backed metabox results
|
||||
|
||||
| Case | Actual | Status |
|
||||
|---|---|---|
|
||||
| positive file | SHA256 `4536c1d7121f48829475f29179f54baa57154b4ef817cf0776f81782585d29ad` | PASS |
|
||||
| shared-prefix xattr | `shared-value` | PASS |
|
||||
| per-file xattr | `value-000` | PASS |
|
||||
| self-loop | mount exit 1; `packed inode nid=38 is not a non-recursive regular file: Integrity check failed` | PASS |
|
||||
| out-of-range | mount exit 1; `Integrity check failed` | PASS |
|
||||
| metabox NID bit 63 | mount exit 1; `Integrity check failed` | PASS |
|
||||
| packed NID bit 63 | mount exit 1; `Integrity check failed` | PASS |
|
||||
|
||||
The `vmstat -m` EROFS row after the matrix showed zero active allocations:
|
||||
|
||||
```text
|
||||
erofs 0 0 240695 16,32,64,128,256,384,1024,2048,4096,8192,16384,32768,65536
|
||||
```
|
||||
|
||||
The cumulative allocation count increased as expected; the active allocation
|
||||
and active-byte columns were both zero.
|
||||
|
||||
## Regression matrix
|
||||
|
||||
| Regression | Actual | Status |
|
||||
|---|---|---|
|
||||
| LZ4 legacy full index | SHA256 `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | PASS |
|
||||
| LZ4 compact index | same SHA256 | PASS |
|
||||
| LZ4 64 KiB big pcluster | same SHA256 | PASS |
|
||||
| LZ4 all-fragments | SHA256 `a3a83e5c524b5ed446a06ce78cf407192a0c80119f15d2bc3489a50515eb49e0` | PASS |
|
||||
| LZ4 ztailpacking | SHA256 `e2aa4a0a0cbcf422f397c7069a38ae0f073781386958e7db0dfa3ff2ca075513` | PASS |
|
||||
| xattr/metabox | file SHA256 `4536c1...`; xattr `value-000` | PASS |
|
||||
| single-device chunk | SHA256 `de29abbd47ecd9136f64f22f73fcb40bce1718a8865e6282e74953aa1df80c44` | PASS |
|
||||
| external compressed LZ4 extent | complete SHA256 `370eb0...` | PASS |
|
||||
| external compressed boundary | offset 65500, 4096 bytes: `0d66627577218a39a620e8b28d8c8d64b974b184063c52cb0cceeaf0c297c0db` | PASS |
|
||||
| local NFSv3/TCP over external compressed extent | complete SHA256 `370eb0...` | PASS |
|
||||
|
||||
The external compressed fixture declared slot 1 at unified block 4, stored the
|
||||
real two-block LZ4 pcluster in the external provider, and zeroed the
|
||||
corresponding bytes in the primary image. Primary SHA256 was
|
||||
`5625612426d68624c77dd94b09754da583fc6bd90767e9e9d958e4fc89131a4d`;
|
||||
blob SHA256 was
|
||||
`0d71102ce471af0a30d4d61a1fbeef1fc33437c85651d7c7f9c60ebd305ec9d0`.
|
||||
|
||||
## NFS startup-race investigation
|
||||
|
||||
The first regression run started rpcbind, mountd, and nfsd and immediately
|
||||
called `mount_nfs`. The client printed one transient
|
||||
`RPCPROG_NFS: RPC: Program not registered`, then retried successfully and read
|
||||
the correct complete SHA256. No dmesg line changed.
|
||||
|
||||
A separate clean rerun waited for:
|
||||
|
||||
```sh
|
||||
rpcinfo -t 127.0.0.1 nfs 3
|
||||
```
|
||||
|
||||
Readiness succeeded on attempt 2:
|
||||
|
||||
```text
|
||||
program 100003 version 3 ready and waiting
|
||||
```
|
||||
|
||||
The subsequent NFS mount emitted no RPC warning and produced the same complete
|
||||
SHA256. This proves the first message was a user-space service-registration
|
||||
race, not an EROFS or NFS data-path failure.
|
||||
|
||||
## dmesg
|
||||
|
||||
Core, regression, and clean NFS snapshots were each byte-identical before and
|
||||
after their respective test matrices. All six snapshot files had SHA256:
|
||||
|
||||
```text
|
||||
2aa3500d33a7cfbe0db87854d3427231cda9cf92b7f85e553f957231f7359680
|
||||
```
|
||||
|
||||
There was no new panic, trap, decompression diagnostic, integrity message, GEOM
|
||||
orphan warning, or NFS kernel message.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Every test used a trap that unmounted the current EROFS/NFS mount, detached the
|
||||
specific md unit, stopped NFS services in client-first order, restored or
|
||||
removed `/etc/exports`, and unloaded the module by the observed KLD ID.
|
||||
|
||||
Final audits after the core, regression, disabled-ZSTD LZ4, and clean NFS runs
|
||||
all reported:
|
||||
|
||||
```text
|
||||
erofs mounts=0
|
||||
NFS mounts=0
|
||||
md units=0
|
||||
erofs modules=0
|
||||
nfsd/mountd/rpcbind processes=0
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- **MKFS-UNAVAILABLE**: erofs-utils 1.8.6 cannot generate the new on-disk extent
|
||||
record format. Extent coverage is independent static ABI/control-flow review
|
||||
only. No newer-tool extent image is counted as a FreeBSD PASS.
|
||||
- METABOX and the transformed HEAD2/partial-reference fixtures require newer
|
||||
format-aware tooling plus byte-level assertions. Their guest reads are real
|
||||
FreeBSD kernel tests; fixture creation and mutations are documented and
|
||||
checksum-validated.
|
||||
- No CI, automated runner, benchmark threshold, memory-pressure run, or forced
|
||||
OOM scenario was added. The requested functional manual matrix and active
|
||||
allocation checks were completed.
|
||||
- Kernel failures: none.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,277 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
fixture_dir=${FIXTURE_DIR:-"$script_dir/fixture"}
|
||||
artifact_dir=${ARTIFACT_DIR:-"$script_dir/artifacts"}
|
||||
|
||||
for tool in mkfs.erofs fsck.erofs dump.erofs python3 sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || {
|
||||
echo "missing tool: $tool" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
test "$(id -u)" -eq 0 || {
|
||||
echo "root is required to create device-node fixtures" >&2
|
||||
exit 1
|
||||
}
|
||||
test ! -e "$fixture_dir" || {
|
||||
echo "fixture directory already exists: $fixture_dir" >&2
|
||||
exit 1
|
||||
}
|
||||
test ! -e "$artifact_dir" || {
|
||||
echo "artifact directory already exists: $artifact_dir" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdir -p \
|
||||
"$fixture_dir/inline" \
|
||||
"$fixture_dir/special" \
|
||||
"$fixture_dir/namei/alpha/bravo/charlie" \
|
||||
"$fixture_dir/namei/alpha/sibling" \
|
||||
"$fixture_dir/namei/wide" \
|
||||
"$fixture_dir/pager" \
|
||||
"$fixture_dir/nfs/basic/subdir" \
|
||||
"$fixture_dir/nlink/subdir" \
|
||||
"$artifact_dir"
|
||||
|
||||
printf 'inline-tail-payload-0123456789\n' > "$fixture_dir/inline/inline.txt"
|
||||
printf 'special target\n' > "$fixture_dir/special/target"
|
||||
ln -s target "$fixture_dir/special/link"
|
||||
mknod "$fixture_dir/special/char-large" c 2748 344865
|
||||
mknod "$fixture_dir/special/block-large" b 2748 344865
|
||||
mkfifo "$fixture_dir/special/fifo"
|
||||
|
||||
printf 'cold nested lookup payload\n' > \
|
||||
"$fixture_dir/namei/alpha/bravo/charlie/payload.txt"
|
||||
printf 'repeat lookup payload\n' > \
|
||||
"$fixture_dir/namei/alpha/bravo/repeat.txt"
|
||||
printf 'sibling marker\n' > "$fixture_dir/namei/alpha/sibling/marker.txt"
|
||||
index=0
|
||||
while [ "$index" -lt 320 ]; do
|
||||
name=$(printf 'entry-%03d-abcdefghijklmnopqrstuvwxyz.txt' "$index")
|
||||
printf 'wide entry %03d\n' "$index" > "$fixture_dir/namei/wide/$name"
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
python3 - "$fixture_dir" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
data = bytes(((index * 131 + 17) ^ (index >> 3)) & 0xff
|
||||
for index in range(5 * 4096 + 731))
|
||||
(root / "pager" / "pager.bin").write_bytes(data)
|
||||
(root / "nfs" / "basic" / "regular.txt").write_text(
|
||||
"stable file handle payload\n", encoding="ascii")
|
||||
(root / "nfs" / "basic" / "subdir" / "child.txt").write_text(
|
||||
"child\n", encoding="ascii")
|
||||
PY
|
||||
ln -s regular.txt "$fixture_dir/nfs/basic/link-to-regular"
|
||||
mkfifo "$fixture_dir/nfs/basic/test.fifo"
|
||||
|
||||
printf 'single\n' > "$fixture_dir/nlink/single.txt"
|
||||
printf 'hardlinked\n' > "$fixture_dir/nlink/hard-a.txt"
|
||||
ln "$fixture_dir/nlink/hard-a.txt" "$fixture_dir/nlink/hard-b.txt"
|
||||
printf 'child\n' > "$fixture_dir/nlink/subdir/child.txt"
|
||||
|
||||
find "$fixture_dir" -exec touch -h -t 197001010000.00 {} +
|
||||
|
||||
build_image()
|
||||
{
|
||||
uuid=$1
|
||||
image=$2
|
||||
source=$3
|
||||
shift 3
|
||||
mkfs.erofs -d0 -x-1 -T0 --all-time --all-root --workers=1 \
|
||||
-U "$uuid" "$@" "$artifact_dir/$image" "$fixture_dir/$source"
|
||||
}
|
||||
|
||||
build_image 11111111-2222-3333-4444-555555555551 inline.erofs inline
|
||||
build_image 11111111-2222-3333-4444-555555555552 \
|
||||
special-compact.erofs special -E force-inode-compact
|
||||
build_image 11111111-2222-3333-4444-555555555553 \
|
||||
special-extended.erofs special -E force-inode-extended
|
||||
build_image 11111111-2222-3333-4444-555555555554 namei-base.erofs namei
|
||||
build_image 11111111-2222-3333-4444-555555555555 \
|
||||
pager-plain.erofs pager -E noinline_data
|
||||
build_image 11111111-2222-3333-4444-555555555556 \
|
||||
pager-lz4.erofs pager -z lz4
|
||||
build_image aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee1 nfs-a.erofs nfs
|
||||
build_image aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee2 nfs-b.erofs nfs
|
||||
build_image 11111111-2222-3333-4444-555555555557 \
|
||||
nlink.erofs nlink -E force-inode-compact
|
||||
|
||||
ARTIFACT_DIR="$artifact_dir" python3 <<'PY'
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
|
||||
artifact_dir = Path(os.environ["ARTIFACT_DIR"])
|
||||
|
||||
|
||||
def u16(image, offset):
|
||||
return struct.unpack_from("<H", image, offset)[0]
|
||||
|
||||
|
||||
def u32(image, offset):
|
||||
return struct.unpack_from("<I", image, offset)[0]
|
||||
|
||||
|
||||
def put_u16(image, offset, value):
|
||||
struct.pack_into("<H", image, offset, value)
|
||||
|
||||
|
||||
def put_u32(image, offset, value):
|
||||
struct.pack_into("<I", image, offset, value)
|
||||
|
||||
|
||||
def update_superblock_checksum(image):
|
||||
polynomial = 0x82F63B78
|
||||
checksum = 0xFFFFFFFF
|
||||
put_u32(image, 1028, 0)
|
||||
for byte in image[1024:4096]:
|
||||
checksum ^= byte
|
||||
for _ in range(8):
|
||||
checksum = (checksum >> 1) ^ (
|
||||
polynomial if checksum & 1 else 0)
|
||||
put_u32(image, 1028, checksum & 0xFFFFFFFF)
|
||||
|
||||
|
||||
def inode_offset(image, nid):
|
||||
block_bits = image[1036]
|
||||
meta_blkaddr = u32(image, 1064)
|
||||
return (meta_blkaddr << block_bits) + (nid << 5)
|
||||
|
||||
|
||||
def compact_inode(image, nid):
|
||||
offset = inode_offset(image, nid)
|
||||
inode_format = u16(image, offset)
|
||||
assert (inode_format & 1) == 0
|
||||
return offset, (inode_format >> 1) & 7, u32(image, offset + 8)
|
||||
|
||||
|
||||
def inline_dir_entries(image, nid):
|
||||
inode = inode_offset(image, nid)
|
||||
inode_format = u16(image, inode)
|
||||
inode_size = 64 if inode_format & 1 else 32
|
||||
layout = (inode_format >> 1) & 7
|
||||
size = (struct.unpack_from("<Q", image, inode + 8)[0]
|
||||
if inode_size == 64 else u32(image, inode + 8))
|
||||
assert layout == 2 and u16(image, inode + 2) == 0
|
||||
data = inode + inode_size
|
||||
first_nameoff = u16(image, data + 8)
|
||||
assert first_nameoff >= 12 and first_nameoff % 12 == 0
|
||||
count = first_nameoff // 12
|
||||
entries = []
|
||||
for index in range(count):
|
||||
entry = data + index * 12
|
||||
child_nid, nameoff = struct.unpack_from("<QH", image, entry)
|
||||
endoff = (u16(image, data + (index + 1) * 12 + 8)
|
||||
if index + 1 < count else size)
|
||||
name = bytes(image[data + nameoff:data + endoff]).split(b"\0", 1)[0]
|
||||
assert name
|
||||
entries.append((child_nid, nameoff, endoff, name))
|
||||
return inode, data, size, entries
|
||||
|
||||
|
||||
def write_image(name, image):
|
||||
update_superblock_checksum(image)
|
||||
(artifact_dir / name).write_bytes(image)
|
||||
|
||||
|
||||
inline = bytearray((artifact_dir / "inline.erofs").read_bytes())
|
||||
_, _, _, inline_root = inline_dir_entries(inline, u16(inline, 1038))
|
||||
inline_nid = next(nid for nid, _, _, name in inline_root if name == b"inline.txt")
|
||||
inline_inode, layout, inline_size = compact_inode(inline, inline_nid)
|
||||
assert layout == 2 and u16(inline, inline_inode + 2) == 0
|
||||
inline_cross_xattr_icount = 695
|
||||
inline_xattr_size = 12 + 4 * (inline_cross_xattr_icount - 1)
|
||||
inline_data_off = inline_inode + 32 + inline_xattr_size
|
||||
block_size = 1 << inline[1036]
|
||||
assert inline_data_off % block_size + inline_size > block_size
|
||||
inline_cross = bytearray(inline)
|
||||
put_u16(inline_cross, inline_inode + 2, inline_cross_xattr_icount)
|
||||
write_image("inline-cross-block.erofs", inline_cross)
|
||||
|
||||
namei = bytearray((artifact_dir / "namei-base.erofs").read_bytes())
|
||||
root_nid = u16(namei, 1038)
|
||||
root_inode, root_data, root_size, root_entries = inline_dir_entries(
|
||||
namei, root_nid)
|
||||
wide_nid = next(nid for nid, _, _, name in root_entries if name == b"wide")
|
||||
wide_inode, wide_layout, _ = compact_inode(namei, wide_nid)
|
||||
assert wide_layout == 2
|
||||
block_bits = namei[1036]
|
||||
wide_block = u32(namei, wide_inode + 16) << block_bits
|
||||
wide_first_nameoff = u16(namei, wide_block + 8)
|
||||
wide_count = wide_first_nameoff // 12
|
||||
wide_last_nameoff = u16(namei, wide_block + (wide_count - 1) * 12 + 8)
|
||||
padding_nul = namei.index(0, wide_block + wide_last_nameoff,
|
||||
wide_block + (1 << block_bits))
|
||||
|
||||
nonzero_padding = bytearray(namei)
|
||||
nonzero_padding[padding_nul + 1:padding_nul + 9] = b"PAD!ERO!"
|
||||
write_image("namei-padding-nonzero.erofs", nonzero_padding)
|
||||
|
||||
short_block = bytearray(namei)
|
||||
put_u32(short_block, root_inode + 8, 8)
|
||||
write_image("namei-corrupt-short.erofs", short_block)
|
||||
|
||||
bad_nameoff = bytearray(namei)
|
||||
first_nameoff = u16(bad_nameoff, root_data + 8)
|
||||
put_u16(bad_nameoff, root_data + 12 + 8, first_nameoff)
|
||||
write_image("namei-corrupt-nameoff.erofs", bad_nameoff)
|
||||
|
||||
bad_name = bytearray(namei)
|
||||
slash_offset = wide_block + wide_last_nameoff + 5
|
||||
assert bad_name[slash_offset] not in (0, ord("/"))
|
||||
bad_name[slash_offset] = ord("/")
|
||||
write_image("namei-corrupt-name.erofs", bad_name)
|
||||
|
||||
nlink = bytearray((artifact_dir / "nlink.erofs").read_bytes())
|
||||
nlink_root_nid = u16(nlink, 1038)
|
||||
_, _, _, nlink_entries = inline_dir_entries(nlink, nlink_root_nid)
|
||||
single_nid = next(nid for nid, _, _, name in nlink_entries
|
||||
if name == b"single.txt")
|
||||
single_inode, _, _ = compact_inode(nlink, single_nid)
|
||||
nlink1 = bytearray(nlink)
|
||||
put_u16(nlink1, single_inode, u16(nlink1, single_inode) | 0x10)
|
||||
put_u16(nlink1, single_inode + 6, 0x1234)
|
||||
write_image("nlink1-patched.erofs", nlink1)
|
||||
|
||||
with (artifact_dir / "fixture-evidence.txt").open("w", encoding="ascii") as out:
|
||||
out.write(f"inline_nid={inline_nid} inode_off={inline_inode} "
|
||||
f"inode_blockoff={inline_inode % block_size} "
|
||||
f"xattr_icount=0->{inline_cross_xattr_icount} "
|
||||
f"inline_data_blockoff={inline_data_off % block_size} "
|
||||
f"inline_size={inline_size}\n")
|
||||
out.write(f"wide_nid={wide_nid} block={wide_block >> block_bits} "
|
||||
f"dirents={wide_count} last_nameoff={wide_last_nameoff} "
|
||||
f"padding_patch={padding_nul + 1 - wide_block}:"
|
||||
f"{padding_nul + 9 - wide_block}\n")
|
||||
out.write(f"nlink1_nid={single_nid} i_format_bit4=1 i_nb=0x1234\n")
|
||||
for image_name in ("special-compact.erofs", "special-extended.erofs"):
|
||||
image = bytearray((artifact_dir / image_name).read_bytes())
|
||||
special_root = u16(image, 1038)
|
||||
_, _, _, entries = inline_dir_entries(image, special_root)
|
||||
out.write(image_name + "\n")
|
||||
for nid, _, _, name in entries:
|
||||
if name not in (b"char-large", b"block-large", b"fifo"):
|
||||
continue
|
||||
offset = inode_offset(image, nid)
|
||||
out.write(f" {name.decode()} nid={nid} inode_size="
|
||||
f"{64 if u16(image, offset) & 1 else 32} "
|
||||
f"raw_u={u32(image, offset + 16):#010x}\n")
|
||||
|
||||
with (artifact_dir / "SHA256SUMS").open("w", encoding="ascii") as sums:
|
||||
for path in sorted(artifact_dir.glob("*.erofs")):
|
||||
sums.write(f"{hashlib.sha256(path.read_bytes()).hexdigest()} "
|
||||
f"{path.name}\n")
|
||||
PY
|
||||
|
||||
fsck.erofs -d1 "$artifact_dir/namei-padding-nonzero.erofs"
|
||||
dump.erofs --cat --path=/wide/entry-079-abcdefghijklmnopqrstuvwxyz.txt \
|
||||
"$artifact_dir/namei-padding-nonzero.erofs"
|
||||
cat "$artifact_dir/fixture-evidence.txt"
|
||||
cat "$artifact_dir/SHA256SUMS"
|
||||
@@ -0,0 +1,166 @@
|
||||
# repo22 Final Review WIP Manual Test Report
|
||||
|
||||
Started: 2026-08-09 01:24 UTC
|
||||
|
||||
Completed: 2026-08-09 03:33 UTC
|
||||
|
||||
Parent commit: `9a3604fba32cfe2ff263d954d80fe588f99db88f`
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
|
||||
Linux reference: `/work/dev-src-linux/fs/erofs`
|
||||
|
||||
FreeBSD reference: `/work/dev-freebsd-releng`
|
||||
|
||||
## Scope
|
||||
|
||||
This run reviews and validates four final correctness findings:
|
||||
|
||||
- Linux-compatible 48-bit superblock union selection;
|
||||
- rejection of extended inode sizes above FreeBSD `OFF_MAX`;
|
||||
- bounded reads from multi-GiB explicit extent holes;
|
||||
- 64-bit directory block-search indexes.
|
||||
|
||||
No CI work, binary fixture, guest overlay, build object, or raw VM artifact is
|
||||
part of the intended commit.
|
||||
|
||||
## Result
|
||||
|
||||
| Test | Result | Evidence |
|
||||
|---|---|---|
|
||||
| TC150 48-bit fallback root | PASS | Real FreeBSD mount, root read, inode and `df` |
|
||||
| TC151 size above `OFF_MAX` | PASS | Six direct failures, rc 1, syscall errno 97 |
|
||||
| TC152 bounded extent hole | PASS | Two pread/mmap probes, stable active allocation |
|
||||
| TC153 large directory index | SHELVED | Host fixture reproducible; kernel run incomplete |
|
||||
|
||||
TC153 is deliberately not marked PASS. See
|
||||
`issues/TC153-large-directory-block-index-validation.md`.
|
||||
|
||||
## Source Review
|
||||
|
||||
Linux `super.c` initializes `blocks_lo` and uses `rb.blocks_hi` only inside
|
||||
`48BIT && rootnid_8b`. repo22 now decodes blocks and root NID together under
|
||||
that same selector.
|
||||
|
||||
FreeBSD exposes signed `off_t` pager and vnode interfaces bounded by
|
||||
`OFF_MAX`; repo22 rejects a larger decoded inode before vnode/pager setup.
|
||||
|
||||
The extent-hole path now zeroes only the current requested span in
|
||||
`z_erofs_do_read()` instead of allocating the complete logical extent.
|
||||
|
||||
The directory block search now uses 64-bit bounds and a checked block-offset
|
||||
multiplication. The within-block search remains 32-bit, matching the validated
|
||||
block-sized domain.
|
||||
|
||||
## Build Results
|
||||
|
||||
Commands:
|
||||
|
||||
```sh
|
||||
EROFS_ZSTDIO=0 ./build.sh
|
||||
EROFS_ZSTDIO=1 ./build.sh
|
||||
nm -u build/erofs.ko
|
||||
git diff --check -- repo-community/repo22
|
||||
```
|
||||
|
||||
| Configuration | Module SHA256 | Result |
|
||||
|---|---|---|
|
||||
| `EROFS_ZSTDIO=0` | `65bc19d53a2a7f0525bfacadb441dab37ee318f5f5b4c62b5ecd1b5090fe46d2` | PASS |
|
||||
| `EROFS_ZSTDIO=1` | `d46ca4dfc858deaf4840fad8b8589b16d4b71b34afa96dc7acf1255890c5cef7` | PASS |
|
||||
|
||||
Both freestanding builds completed. Neither module had an unresolved `bcmp`
|
||||
reference. The ZSTDIO-enabled module was transferred to the guest, loaded as
|
||||
KLD ID 7, and unloaded after testing.
|
||||
|
||||
## Fixture Evidence
|
||||
|
||||
| Fixture | SHA256 |
|
||||
|---|---|
|
||||
| `fallback-48bit-root2.erofs` | `bed3be4dfb8499d4e794b03eddd5b9cda95bc8e5571ae7d765c4852ece0d95a3` |
|
||||
| `extended-size-bit63.erofs` | `e4a0f550168f1a2911603863d0074d474e61adc787c14c0278c83a060643ee38` |
|
||||
| `extent-hole-5g.erofs` | `50014a24493918247e36511ad34a2fe8ab47ae09ea46d7fd62a1bed445a6c65f` |
|
||||
| `large-dir-intmax.erofs` | `0f90d3d57adbbbd946e41b225c1f6c464915c6abb0b13478ec9b2a318def1f72` |
|
||||
|
||||
The TC153 hash is host generator evidence only.
|
||||
|
||||
## TC150
|
||||
|
||||
The fixture encoded `rootnid_2b=36`, `rootnid_8b=0`, `blocks_lo=1`, and the
|
||||
48-bit incompat bit. The 4 KiB image mounted on `/dev/md0` and produced:
|
||||
|
||||
```text
|
||||
content=48-bit fallback root
|
||||
sha256=d361f537492113ca93cfbf91c06ebc06e2b8b695d8b6e63ad3666013eaa029f0
|
||||
root inode=36
|
||||
df total=4 one-KiB blocks
|
||||
```
|
||||
|
||||
This proves the union was not shifted into a high block count. It does not
|
||||
replace the large-provider TC010 test.
|
||||
|
||||
## TC151
|
||||
|
||||
The fixture used extended inode NID 40 at byte 1280 with
|
||||
`i_size=0x8000000000000000`. The image mounted, but every access to `big.dat`
|
||||
failed before open or pager setup:
|
||||
|
||||
| Access | Attempts | Direct rc | `truss` result |
|
||||
|---|---:|---:|---|
|
||||
| `stat` | 2 | 1, 1 | `fstatat ... ERR#97` |
|
||||
| `cat` | 2 | 1, 1 | `openat ... ERR#97` |
|
||||
| `mmap_fault` | 2 | 1, 1 | `openat ... ERR#97` |
|
||||
|
||||
All six errors were `Integrity check failed`. No file descriptor reached the
|
||||
read or mmap phase.
|
||||
|
||||
## TC152
|
||||
|
||||
The mounted file size was `5368713216` bytes. The native helper probed offset
|
||||
`3221225472` with a one-byte pread and one-page private mmap:
|
||||
|
||||
```text
|
||||
attempt 1: PASS, real 0.03 s
|
||||
attempt 2: PASS, real 0.02 s
|
||||
```
|
||||
|
||||
The `erofs` malloc row was `3` active allocations and `768` active bytes both
|
||||
before and after. The cumulative allocation counter moved from 89 to 95, as
|
||||
expected for temporary request buffers; active memory did not scale with the
|
||||
5 GiB hole.
|
||||
|
||||
## TC153
|
||||
|
||||
The first 4096-byte fixture was Layout 2 and failed before the target namei
|
||||
path. A second 65536-byte base with 400 entries was still Layout 2. The tracked
|
||||
generator now converts the directory to Layout 0 by moving its complete data
|
||||
to appended blocks and produced a 90112-byte patched image with raw block 16,
|
||||
22 image blocks, and the hash listed above.
|
||||
|
||||
The corrected fixture was not executed in the FreeBSD kernel during this run.
|
||||
TC153 remains SHELVED, and the complete attempt history and acceptance criteria
|
||||
are in its issue document.
|
||||
|
||||
## Cleanup
|
||||
|
||||
The qualified rerun used dmesg line count 123 before and after. It ended with:
|
||||
|
||||
```text
|
||||
EROFS mounts: 0
|
||||
md providers: none
|
||||
EROFS modules: 0
|
||||
```
|
||||
|
||||
An earlier unqualified probe encountered a preloaded differently named EROFS
|
||||
KLD and a `truss` exit-status ambiguity. It was discarded. The qualified run
|
||||
first unloaded that KLD, loaded the exact module hash above, captured direct
|
||||
command exit codes separately from syscall traces, and then cleaned up.
|
||||
|
||||
## Deferred Issues
|
||||
|
||||
- `issues/TC153-large-directory-block-index-validation.md`
|
||||
- `issues/TC010-48bit-statfs-large-provider.md`
|
||||
- `issues/extent-metadata-fixture-unavailable.md`
|
||||
|
||||
The raw guest transcripts remain untracked under `/work/build`. This report,
|
||||
the deterministic generator, and source/test documentation are the tracked
|
||||
evidence.
|
||||
@@ -0,0 +1,271 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
fixture_dir=${FIXTURE_DIR:-"$script_dir/fixture"}
|
||||
artifact_dir=${ARTIFACT_DIR:-"$script_dir/artifacts"}
|
||||
|
||||
for tool in mkfs.erofs python3 sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || {
|
||||
echo "missing tool: $tool" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
test ! -e "$fixture_dir" || {
|
||||
echo "fixture directory already exists: $fixture_dir" >&2
|
||||
exit 1
|
||||
}
|
||||
test ! -e "$artifact_dir" || {
|
||||
echo "artifact directory already exists: $artifact_dir" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdir -p "$fixture_dir/fallback" "$fixture_dir/oversize" \
|
||||
"$fixture_dir/extent" "$fixture_dir/large/huge" "$artifact_dir"
|
||||
|
||||
FIXTURE_DIR="$fixture_dir" python3 <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
root = Path(os.environ["FIXTURE_DIR"])
|
||||
(root / "fallback" / "root.txt").write_text(
|
||||
"48-bit fallback root\n", encoding="ascii")
|
||||
(root / "oversize" / "big.dat").write_bytes(b"x")
|
||||
(root / "extent" / "hole.dat").write_bytes(b"\0" * (1024 * 1024))
|
||||
(root / "large" / "huge" / "anchor.txt").write_text(
|
||||
"large directory anchor\n", encoding="ascii")
|
||||
for index in range(400):
|
||||
(root / "large" / "huge" /
|
||||
f"entry-{index:03d}-abcdefghijklmnopqrstuvwxyz.txt").write_text(
|
||||
f"entry {index:03d}\n", encoding="ascii")
|
||||
PY
|
||||
find "$fixture_dir" -exec touch -h -t 197001010000.00 {} +
|
||||
|
||||
build_image()
|
||||
{
|
||||
uuid=$1
|
||||
image=$2
|
||||
source=$3
|
||||
shift 3
|
||||
mkfs.erofs -d0 -x-1 -T0 --all-time --all-root --workers=1 \
|
||||
-U "$uuid" "$@" "$artifact_dir/$image" "$fixture_dir/$source"
|
||||
}
|
||||
|
||||
build_image 22222222-3333-4444-5555-666666666650 \
|
||||
fallback-base.erofs fallback -E force-inode-compact
|
||||
build_image 22222222-3333-4444-5555-666666666651 \
|
||||
oversize-base.erofs oversize -E force-inode-extended
|
||||
build_image 22222222-3333-4444-5555-666666666652 \
|
||||
extent-base.erofs extent -E legacy-compress,force-inode-extended -z lz4
|
||||
build_image 22222222-3333-4444-5555-666666666653 \
|
||||
large-dir-base.erofs large -E force-inode-extended
|
||||
|
||||
ARTIFACT_DIR="$artifact_dir" python3 <<'PY'
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
|
||||
artifact_dir = Path(os.environ["ARTIFACT_DIR"])
|
||||
SUPER = 1024
|
||||
FEATURE_INCOMPAT = SUPER + 80
|
||||
ROOTNID_2B = SUPER + 14
|
||||
BLOCKS_LO = SUPER + 36
|
||||
META_BLKADDR = SUPER + 40
|
||||
ROOTNID_8B = SUPER + 112
|
||||
EROFS_FEATURE_INCOMPAT_48BIT = 0x80
|
||||
EROFS_INODE_COMPRESSED_FULL = 1
|
||||
Z_EROFS_ADVISE_EXTENTS = 0x1
|
||||
Z_EROFS_EXTENT_RECSZ_16 = 0x4
|
||||
HOLE_SIZE = 5 * 1024 * 1024 * 1024 + 4096
|
||||
HUGE_DIR_BLOCKS = (1 << 31) + 1
|
||||
|
||||
|
||||
def u16(image, offset):
|
||||
return struct.unpack_from("<H", image, offset)[0]
|
||||
|
||||
|
||||
def u32(image, offset):
|
||||
return struct.unpack_from("<I", image, offset)[0]
|
||||
|
||||
|
||||
def u64(image, offset):
|
||||
return struct.unpack_from("<Q", image, offset)[0]
|
||||
|
||||
|
||||
def put_u16(image, offset, value):
|
||||
struct.pack_into("<H", image, offset, value)
|
||||
|
||||
|
||||
def put_u32(image, offset, value):
|
||||
struct.pack_into("<I", image, offset, value)
|
||||
|
||||
|
||||
def put_u64(image, offset, value):
|
||||
struct.pack_into("<Q", image, offset, value)
|
||||
|
||||
|
||||
def update_superblock_checksum(image):
|
||||
polynomial = 0x82F63B78
|
||||
checksum = 0xFFFFFFFF
|
||||
block_size = 1 << image[SUPER + 12]
|
||||
put_u32(image, SUPER + 4, 0)
|
||||
for byte in image[SUPER:block_size]:
|
||||
checksum ^= byte
|
||||
for _ in range(8):
|
||||
checksum = (checksum >> 1) ^ (
|
||||
polynomial if checksum & 1 else 0)
|
||||
put_u32(image, SUPER + 4, checksum & 0xFFFFFFFF)
|
||||
|
||||
|
||||
def inode_offset(image, nid):
|
||||
block_bits = image[SUPER + 12]
|
||||
return (u32(image, META_BLKADDR) << block_bits) + (nid << 5)
|
||||
|
||||
|
||||
def inode_info(image, nid):
|
||||
offset = inode_offset(image, nid)
|
||||
inode_format = u16(image, offset)
|
||||
inode_size = 64 if inode_format & 1 else 32
|
||||
size = u64(image, offset + 8) if inode_size == 64 else u32(
|
||||
image, offset + 8)
|
||||
xattr_count = u16(image, offset + 2)
|
||||
xattr_size = 0 if xattr_count == 0 else 12 + 4 * (xattr_count - 1)
|
||||
layout = (inode_format >> 1) & 7
|
||||
return offset, inode_format, inode_size, xattr_size, layout, size
|
||||
|
||||
|
||||
def directory_entries(image, nid):
|
||||
offset, _, inode_size, xattr_size, layout, size = inode_info(image, nid)
|
||||
block_size = 1 << image[SUPER + 12]
|
||||
assert 0 < size <= block_size
|
||||
if layout == 2:
|
||||
data = offset + inode_size + xattr_size
|
||||
elif layout == 0:
|
||||
data = u32(image, offset + 16) << image[SUPER + 12]
|
||||
else:
|
||||
raise AssertionError(f"unsupported directory layout {layout}")
|
||||
first_nameoff = u16(image, data + 8)
|
||||
assert first_nameoff >= 12 and first_nameoff % 12 == 0
|
||||
count = first_nameoff // 12
|
||||
entries = []
|
||||
for index in range(count):
|
||||
entry = data + index * 12
|
||||
child_nid = u64(image, entry)
|
||||
nameoff = u16(image, entry + 8)
|
||||
endoff = (u16(image, entry + 20)
|
||||
if index + 1 < count else size)
|
||||
name = bytes(image[data + nameoff:data + endoff]).split(b"\0", 1)[0]
|
||||
assert name
|
||||
entries.append((name, child_nid))
|
||||
return entries
|
||||
|
||||
|
||||
def child_nid(image, parent_nid, name):
|
||||
return next(nid for entry_name, nid in directory_entries(image, parent_nid)
|
||||
if entry_name == name)
|
||||
|
||||
|
||||
def convert_inline_directory_to_plain(image, nid):
|
||||
offset, inode_format, inode_size, xattr_size, layout, size = inode_info(
|
||||
image, nid)
|
||||
assert layout == 2 and inode_size == 64 and size > 0
|
||||
block_size = 1 << image[SUPER + 12]
|
||||
tail_size = size % block_size
|
||||
assert tail_size > 0
|
||||
raw_block = u32(image, offset + 16)
|
||||
full_size = size - tail_size
|
||||
inline_offset = offset + inode_size + xattr_size
|
||||
directory_data = bytes(
|
||||
image[raw_block * block_size:raw_block * block_size + full_size] +
|
||||
image[inline_offset:inline_offset + tail_size])
|
||||
assert len(directory_data) == size
|
||||
|
||||
new_raw_block = (len(image) + block_size - 1) // block_size
|
||||
image.extend(b"\0" * (new_raw_block * block_size - len(image)))
|
||||
image.extend(directory_data)
|
||||
image.extend(b"\0" * (-len(image) % block_size))
|
||||
put_u16(image, offset, inode_format & ~(7 << 1))
|
||||
put_u32(image, offset + 16, new_raw_block)
|
||||
put_u32(image, BLOCKS_LO, len(image) // block_size)
|
||||
assert inode_info(image, nid)[4] == 0
|
||||
return new_raw_block, len(image) // block_size
|
||||
|
||||
|
||||
def write_image(name, image):
|
||||
update_superblock_checksum(image)
|
||||
(artifact_dir / name).write_bytes(image)
|
||||
|
||||
|
||||
evidence = []
|
||||
|
||||
fallback = bytearray((artifact_dir / "fallback-base.erofs").read_bytes())
|
||||
fallback_root = u16(fallback, ROOTNID_2B)
|
||||
fallback_blocks = u32(fallback, BLOCKS_LO)
|
||||
assert fallback_root != 0 and u64(fallback, ROOTNID_8B) == 0
|
||||
put_u32(fallback, FEATURE_INCOMPAT,
|
||||
u32(fallback, FEATURE_INCOMPAT) | EROFS_FEATURE_INCOMPAT_48BIT)
|
||||
put_u64(fallback, ROOTNID_8B, 0)
|
||||
write_image("fallback-48bit-root2.erofs", fallback)
|
||||
evidence.append(
|
||||
f"fallback rootnid_2b={fallback_root} rootnid_8b=0 "
|
||||
f"blocks_lo={fallback_blocks} union=0x{u16(fallback, ROOTNID_2B):04x}")
|
||||
|
||||
oversize = bytearray((artifact_dir / "oversize-base.erofs").read_bytes())
|
||||
oversize_root = u16(oversize, ROOTNID_2B)
|
||||
oversize_nid = child_nid(oversize, oversize_root, b"big.dat")
|
||||
oversize_off, oversize_format, oversize_isize, _, _, _ = inode_info(
|
||||
oversize, oversize_nid)
|
||||
assert oversize_format & 1 and oversize_isize == 64
|
||||
put_u64(oversize, oversize_off + 8, 1 << 63)
|
||||
write_image("extended-size-bit63.erofs", oversize)
|
||||
evidence.append(
|
||||
f"oversize nid={oversize_nid} inode_off={oversize_off} "
|
||||
f"i_size=0x{u64(oversize, oversize_off + 8):016x}")
|
||||
|
||||
extent = bytearray((artifact_dir / "extent-base.erofs").read_bytes())
|
||||
extent_root = u16(extent, ROOTNID_2B)
|
||||
extent_nid = child_nid(extent, extent_root, b"hole.dat")
|
||||
extent_off, extent_format, extent_isize, extent_xattr, extent_layout, _ = \
|
||||
inode_info(extent, extent_nid)
|
||||
assert extent_format & 1 and extent_isize == 64
|
||||
assert extent_layout == EROFS_INODE_COMPRESSED_FULL
|
||||
header = (extent_off + extent_isize + extent_xattr + 7) & ~7
|
||||
record = (header + 8 + 15) & ~15
|
||||
root_off = inode_offset(extent, extent_root)
|
||||
assert not (header < root_off + 64 and root_off < record + 16)
|
||||
put_u64(extent, extent_off + 8, HOLE_SIZE)
|
||||
struct.pack_into("<IHH", extent, header, 1,
|
||||
Z_EROFS_ADVISE_EXTENTS | Z_EROFS_EXTENT_RECSZ_16, 0)
|
||||
struct.pack_into("<IIII", extent, record, 0, 0, 0, 0)
|
||||
write_image("extent-hole-5g.erofs", extent)
|
||||
evidence.append(
|
||||
f"extent-hole nid={extent_nid} inode_off={extent_off} size={HOLE_SIZE} "
|
||||
f"header={header} record={record} extents=1 recsize=16 plen=0 lstart=0")
|
||||
|
||||
large = bytearray((artifact_dir / "large-dir-base.erofs").read_bytes())
|
||||
large_root = u16(large, ROOTNID_2B)
|
||||
large_nid = child_nid(large, large_root, b"huge")
|
||||
large_off, large_format, large_isize, _, _, _ = inode_info(large, large_nid)
|
||||
assert large_format & 1 and large_isize == 64
|
||||
large_raw_block, large_image_blocks = convert_inline_directory_to_plain(
|
||||
large, large_nid)
|
||||
large_size = HUGE_DIR_BLOCKS * (1 << large[SUPER + 12])
|
||||
put_u64(large, large_off + 8, large_size)
|
||||
write_image("large-dir-intmax.erofs", large)
|
||||
evidence.append(
|
||||
f"large-dir nid={large_nid} inode_off={large_off} size={large_size} "
|
||||
f"blocks={HUGE_DIR_BLOCKS} last_block={HUGE_DIR_BLOCKS - 1} "
|
||||
f"layout=flat-plain raw_block={large_raw_block} "
|
||||
f"image_blocks={large_image_blocks}")
|
||||
|
||||
(artifact_dir / "fixture-evidence.txt").write_text(
|
||||
"\n".join(evidence) + "\n", encoding="ascii")
|
||||
with (artifact_dir / "SHA256SUMS").open("w", encoding="ascii") as sums:
|
||||
for path in sorted(artifact_dir.glob("*.erofs")):
|
||||
sums.write(f"{hashlib.sha256(path.read_bytes()).hexdigest()} "
|
||||
f"{path.name}\n")
|
||||
PY
|
||||
|
||||
cat "$artifact_dir/fixture-evidence.txt"
|
||||
cat "$artifact_dir/SHA256SUMS"
|
||||
@@ -0,0 +1,98 @@
|
||||
# repo22 Review Fixes Manual Test Report
|
||||
|
||||
Started: 2026-08-09 05:31 UTC
|
||||
|
||||
Completed: 2026-08-09 05:57 UTC
|
||||
|
||||
Parent commit: `077b37ab44780a31f6cd76045afe6e0a92f3a5be`
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
|
||||
|
||||
## Scope and Result
|
||||
|
||||
| Test | Result | Evidence |
|
||||
|---|---|---|
|
||||
| TC154 per-inode NFS generation | PASS | Same-image remount, same-superblock replacement, malformed replacement |
|
||||
| TC155 explicit extent `pa + plen` overflow | PASS | Repeated `ERR#97` and DTrace device-I/O observation |
|
||||
| TC156 inode timestamp validation | PASS | Five compact/extended corruption fixtures returned `ERR#97` |
|
||||
|
||||
## Build Results
|
||||
|
||||
Both builds ran natively in the guest with
|
||||
`FREEBSD_SRC=/tmp/repo22-freebsd15-src` and completed with kernel `-Werror`.
|
||||
|
||||
| Configuration | Module SHA256 | Result |
|
||||
|---|---|---|
|
||||
| `WITH_ZSTDIO=0` | `d6e755c7a75dd5043b603d58215ae3e7c8f29318b31dc1885aa9bb070d6e57a5` | PASS |
|
||||
| `WITH_ZSTDIO=1` | `b442f9f05af1d1a76801690c83c9a40ec6fc739adb0663e7dacaf837eeb2ecf3` | PASS |
|
||||
|
||||
The disabled module had no undefined `ZSTD_*` or `bcmp` symbol. The enabled
|
||||
module referenced only the expected FreeBSD kernel ZSTD API symbols and no
|
||||
`bcmp`.
|
||||
|
||||
## Fixture Reproduction
|
||||
|
||||
`python3 tests/review_fixtures.py make` was run twice in independent empty
|
||||
directories with erofs-utils 1.8.6. All nine images were byte-identical between
|
||||
runs, and both `SHA256SUMS` files self-verified.
|
||||
|
||||
| Fixture | SHA256 |
|
||||
|---|---|
|
||||
| `nfs-inode-a.erofs` | `307879542d5bb52ff02fdb12553fee9a94cd1360b99752ebd08bedf9c9dc1c8c` |
|
||||
| `nfs-inode-b.erofs` | `64ddb0efcb3c5bd092710a3d3a5ea7aaf637c070e2e061dbf9d4538262fa2f49` |
|
||||
| `nfs-inode-corrupt.erofs` | `5f49bcd9361531c53f6435f53fb10acc579a8ff7fb6716d78da9dff60f7e8539` |
|
||||
| `extent-pa-wrap.erofs` | `1e49ecf1917265fde9b606e80f7dbae8b14aca8c6f149128b31d6c825da546dd` |
|
||||
| `compact-epoch-range.erofs` | `3e031196266fe308f5443878cea7afd775c797856dfb4e9ed28d14bf71d81abb` |
|
||||
| `compact-epoch-wrap.erofs` | `2c3971c42a85494a75317c05b6da3669ea17a0301b7aa36517048d5e6de60259` |
|
||||
| `compact-nsec-invalid.erofs` | `b7137b4b18c121c613689c308d5938339ea3e867723046e268d1949e8328c27b` |
|
||||
| `extended-nsec-invalid.erofs` | `2d8a05d993155895feba67b2ff2dcad224a590c798925c31708c1b284c7c7ef3` |
|
||||
| `extended-seconds-range.erofs` | `aa9675ac3e857892d182b033bb9f96424364aace84a877d2001f47fa32503e45` |
|
||||
|
||||
## TC154
|
||||
|
||||
Image A reported FID `len=16`, `pad=0`, NID 452, and generation
|
||||
`3895653226`; `fhstat`, `stat -f %v`, and the fixture manifest agreed. Reusing
|
||||
md90 for an unchanged-image remount preserved the complete handle and the old
|
||||
handle remained readable.
|
||||
|
||||
Image B retained fsid `00000034:000000e0` and NID 452 but reported generation
|
||||
`548470773`. Both `fhstat` and `fhopen` on A's handle returned `ESTALE`; B's
|
||||
handle succeeded. With the malformed replacement, both old-handle operations
|
||||
returned positive FreeBSD errno 45 (`EOPNOTSUPP`).
|
||||
|
||||
## TC155
|
||||
|
||||
The fixture mounted and `stat` reported the 1 MiB target. Two direct reads at
|
||||
logical offset 4096 exited 1; both independent truss runs showed:
|
||||
|
||||
```text
|
||||
read(..., 1) ERR#97 'Integrity check failed'
|
||||
```
|
||||
|
||||
An `io:::start` DTrace positive control captured a raw md90 read at physical
|
||||
offset 4096. The failing EROFS read produced no md90 I/O event, proving the
|
||||
wrapped physical address did not reach the provider.
|
||||
|
||||
## TC156
|
||||
|
||||
`compact-epoch-wrap` and `compact-nsec-invalid` failed mount directly with rc 1
|
||||
and traced `nmount(...)=ERR#97`. `compact-epoch-range` mounted with root mtime
|
||||
`INT64_MAX`, while target stat returned `ERR#97`. Both extended target fixtures
|
||||
mounted successfully and returned `ERR#97` for invalid nanoseconds and
|
||||
`INT64_MAX+1` seconds. Every failing operation was executed directly and again
|
||||
under truss.
|
||||
|
||||
## Harness Notes
|
||||
|
||||
The first build invocation used absent `/usr/src` and was rerun with the actual
|
||||
guest source path. One test attempt used an overly narrow KLD filename match and
|
||||
executed no TC step. A second attempt discarded `truss`'s zero wrapper status;
|
||||
the qualified runs record direct command status separately from traced syscall
|
||||
errno. None of these discarded attempts is counted as PASS evidence.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Each qualified run ended with unchanged dmesg line count (`123 -> 123`), zero
|
||||
EROFS mounts, no md provider, and zero EROFS modules. Raw transcripts, build
|
||||
objects, modules, and generated images remain outside the repository under
|
||||
`/work/build` and guest `/tmp` only.
|
||||
@@ -0,0 +1,159 @@
|
||||
# repo22 G1 Full Manual Regression Report
|
||||
|
||||
Execution window: 2026-08-09 06:30-07:10 UTC
|
||||
|
||||
Code baseline: `cd0e985b5ac54a4b7acb7042422329ad1729fb3e`
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG, FreeBSD clang 19.1.7
|
||||
|
||||
## Scope and Method
|
||||
|
||||
This run covers exactly these 32 test cases and no others:
|
||||
|
||||
`TC001, TC007, TC009, TC011-TC014, TC019-TC040, TC147, TC150, TC151`
|
||||
|
||||
Every test was executed directly from its Markdown procedure. No CI, runner,
|
||||
or wrapper supplied a result, and no historical run was accepted as current
|
||||
evidence. Each test used a fresh dynamically allocated md provider and was
|
||||
cleaned before the next test.
|
||||
|
||||
Set audit: requested 32, selected 32, unique 32, duplicate 0, missing 0,
|
||||
extra 0.
|
||||
|
||||
## Exact Build
|
||||
|
||||
The source archive exported from the code baseline had SHA256
|
||||
`5b7fd2ac32ecf791f71c74849a6e8c1775e442d1be3c7852c654d074b2c8351b`.
|
||||
The module was built natively in the guest with:
|
||||
|
||||
```text
|
||||
FREEBSD_SRC=/tmp/repo22-freebsd15-src WITH_ZSTDIO=0 ./build.sh
|
||||
```
|
||||
|
||||
The build completed with kernel `-Werror`. The module had no unresolved
|
||||
`bcmp` or `ZSTD_*` symbols.
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Source commit | `cd0e985b5ac54a4b7acb7042422329ad1729fb3e` |
|
||||
| `WITH_ZSTDIO` | `0` |
|
||||
| KLD SHA256 | `023ddf0ea2205977f5921715d2a6206b607806840180ddf470103fd1d6deba84` |
|
||||
| Loaded module | KLD ID 20, size `0xb8a8`, `erofs.ko` |
|
||||
| Guest userland/kernel | `15.0-RELEASE-p8` / `15.0-RELEASE-p8` |
|
||||
|
||||
The later documentation-only progress commit `573aaab7ee0b47cb6aed7f76c52c68dd4041326b`
|
||||
did not alter `src/`; the KLD therefore remains an exact build of the stated
|
||||
code baseline.
|
||||
|
||||
## Fixture Qualification
|
||||
|
||||
`tests/prepare_g1_fixtures.sh` generated deterministic sources and production
|
||||
erofs-utils 1.8.6 images. `tests/g1_fixtures.py` applied structured,
|
||||
field-checked transforms and recalculated checksums. Independent empty output
|
||||
directories `repo22-g1-repro-c.6IK7SM` and `repo22-g1-repro-d.egVQgj` produced
|
||||
byte-identical source manifests, metadata, image manifests, and structured
|
||||
evidence.
|
||||
|
||||
| Manifest | SHA256 |
|
||||
|---|---|
|
||||
| `SOURCE-SHA256SUMS` | `5ce53780f455208cb1efbf2738429a1f6f5c4eb3af1663779815c6afc0fea2bc` |
|
||||
| `SOURCE-METADATA` | `bd27480c238ffff441d328ab71775989611d057ffaed691dae0238f738371e46` |
|
||||
| `images/IMAGE-SHA256SUMS` | `54fb256b3847acc8b1e735eacaace56c1747f65237ee5730b2b5fa3e3ab2b0f3` |
|
||||
| `images/fixture-evidence.txt` | `84e8e2e595a88e034ea0a05777207d4e3430e4e764a7fbc4d4a4770defcca3db` |
|
||||
|
||||
| Image | SHA256 |
|
||||
|---|---|
|
||||
| `compact-dot-omitted.erofs` | `bbd1d3d9f619a71ed7a874feb8c437aa9bf3e86f671abfa243a85de57e809d54` |
|
||||
| `compact-nlink1.erofs` | `c9ecfc29fbd45a2c37c6422d689ac340943dbc23ee4428ffd4698ef88e0b331d` |
|
||||
| `compact.erofs` | `41004ed19d58e698f277cb268b570b87203c2ba08ea4a68a72f6e3b1c7aec47a` |
|
||||
| `extended-large-hole.erofs` | `7a69d791742096ddfece20c7a793d828ec7e887352d0b62f22839f66ecb26a6d` |
|
||||
| `extended-size-bit63.erofs` | `9413a4197f5b2c43876e257690a5f96e82f61b2b62084e2df688144070f0825b` |
|
||||
| `extended.erofs` | `3d96fd292749f1ef7ae72f8e370fc8a94231ed575fb36a6d66784a59d8c4ffa7` |
|
||||
| `fallback-48bit-root2.erofs` | `8217da2ae664358e12d357524ef0bb52a897f1ed206b2353c2a2033b76d4ae79` |
|
||||
| `flat.erofs` | `3785ca07e7bd16f6c611191596ae0314253c0ae9b7217a4b22de25799b0f08ac` |
|
||||
| `inline-cross-block.erofs` | `354b674fd144cab93403d47ad95135968c24d41b34a8c908aa6717882243e936` |
|
||||
| `inline-zero.erofs` | `c894f249ca6d4a7a16c7eb728687c6aba1f948630c4f72e20680655c0fca70c0` |
|
||||
| `inline.erofs` | `1474c310a80766666c1d578174ca03972fb9e03dff8c2357a37883151d4acc47` |
|
||||
| `invalid-dirent-nid.erofs` | `309d6829ebc87c22ae6ba23f07ae0b00a62c7e657c3cc2ec46fe6a64d24eea69` |
|
||||
| `root8-48bit.erofs` | `2f48797586395ff7d2204d43e67d6e2aaaa04c9bdfb162c5c5e2c24dc6e88893` |
|
||||
|
||||
The corrected dot-omitted directory has size 35 and on-disk names
|
||||
`..,child.txt`. Its encoding was independently checked with erofs-utils 1.9.3,
|
||||
including a passing `fsck.erofs`; erofs-utils 1.8.6 does not recognize the
|
||||
48-bit incompat feature and was not used to claim support for that image.
|
||||
|
||||
## Per-Test Results
|
||||
|
||||
| Test | Result | Actual command summary and observable evidence | Cleanup |
|
||||
|---|---|---|---|
|
||||
| TC001 | PASS | `mdconfig`, `mount -t erofs -o ro`, `cmp`, `sha256`, `statfs_probe`; root source/mount SHA `04690aad...3105`, blocks 17, files 15, readonly 1. | mount 0, md 0 |
|
||||
| TC007 | PASS | Two fresh providers and concurrent mounts; both waits returned 0, and source/mount1/mount2 `testfile` SHA was `0e5303ad...e3b1`. | mounts 0, md units 0 |
|
||||
| TC009 | PASS | `statfs_probe`, `df -kT`, `df -iT`; bsize 4096, blocks 17, free 0, files 15, ffree 0, 68 KiB used, readonly 1. | mount 0, md 0 |
|
||||
| TC011 | PASS | `stat`, complete top-level name comparison, root payload `cmp`; root inode 36, directory mode 0755, nlink 4, root SHA `04690aad...3105`. | mount 0, md 0 |
|
||||
| TC012 | PASS | `getfh`, `fhstat`, `fhopen`, repeated handle comparison; fsid `00000063:000000e0`, NID 71, generation 2262931519, data SHA `0e5303ad...e3b1`. | mount 0, md 0, outputs 0 |
|
||||
| TC013 | PASS | Patched dirent NID 59 to 3200 with valid CRC; two `read_probe expect-error` calls returned errno 97 and truss showed `fstatat ERR#97`; dmesg `123->123`. | mount 0, md 0, outputs 0 |
|
||||
| TC014 | PASS | Structured superblock inspect plus mount/statfs; magic `0xe0f5e1e2`, block bits 12, root NID 36, blocks 17, inodes 15, dmesg `123->123`. | mount 0, md 0 |
|
||||
| TC019 | PASS | Mounted actual `feature=0x80 rootnid_8b=36 blocks_hi=0` image; root inode 36, blocks 17, root SHA `04690aad...3105`. | mount 0, md 0 |
|
||||
| TC020 | PASS | `stat`, `cmp`, `sha256` on compact `small.txt`; NID 65, size 22, nlink 1, blocks 8, SHA `6e0d152a...`. | mount 0, md 0 |
|
||||
| TC021 | PASS | Field evidence `i_format=0x14 i_nb=0x1234`; mounted NID 63 reported nlink 1 and source-exact SHA `ff151c...`. | mount 0, md 0 |
|
||||
| TC022 | PASS | Structured raw rdev `0x543abc21`; native special-node probe reported char/block major 2748, minor 344865, while FIFO rdev was `NODEV`. | mount 0, md 0 |
|
||||
| TC023 | PASS | Extended inode NID 45, 64-byte inode, size 2097883; `stat`, full `cmp`, SHA `eed000b0...` all matched source. | mount 0, md 0 |
|
||||
| TC024 | PASS | Size 4294971393 sparse fixture; three 65536-byte reads at offsets 0, 2147483648, and 4294905857 matched deterministic zero source SHA `de2f2560...`. | mount 0, md 0, outputs 0 |
|
||||
| TC025 | PASS | Baseline and patched mounts checked `single` NID 63/nlink 1, `hard-a` and `hard-b` NID 57/nlink 2, and `dotdir` NID 53/nlink 2; all source comparisons passed. | mounts 0, md units 0 |
|
||||
| TC026 | PASS | Valid bit-4 fixture evidence: size 35, on-disk `..,child.txt`, root8 36; listing synthesized `.`, retained `..`, and child SHA matched `de256820...41f0`. | mount 0, md 0 |
|
||||
| TC027 | PASS | Host `dump.erofs --ls --path=/dotdir` showed explicit `.,..,child.txt`; guest listing and child `cmp` matched SHA `de256820...41f0`. | mount 0, md 0 |
|
||||
| TC028 | PASS | `flat.erofs` layout 0 small file; size 25, blocks 8, source/mount SHA `bb9c1e27...f486`. | mount 0, md 0 |
|
||||
| TC029 | PASS | Full medium-file `cmp` SHA `471108a5...12e6`; `pread` offset 40960 length 20480 matched independent range SHA `72142972...5fb`. | mount 0, md 0, output 0 |
|
||||
| TC030 | PASS | 10485791-byte full `dd`/`cmp` SHA `0b39b8a4...c56e`; 1 MiB read at 5 MiB matched SHA `c26bb16b...ba5c`. | mount 0, md 0, outputs 0 |
|
||||
| TC031 | PASS | Inline `tiny.txt` evidence layout 2; size 12, blocks 8, source/mount SHA `863f7088...73f38`. | mount 0, md 0 |
|
||||
| TC032 | PASS | Explicit layout-2 zero-length inode; size 0, blocks 0, EOF clean, both hashes `e3b0c442...b855`. | mount 0, md 0 |
|
||||
| TC033 | PASS | 5000-byte tailpacked file full SHA `e4c7fbec...b2a6`; 64-byte range crossing offset 4096 matched SHA `6ff6b010...c0e`. | mount 0, md 0, outputs 0 |
|
||||
| TC034 | PASS | Evidence layouts for 4095/4096/4097 were 0/0/2; full SHAs `5255d8c8...`, `677802c8...`, `eda39df0...`; nine-byte boundary range SHA `9e67b4e5...182c`. | mount 0, md 0, outputs 0 |
|
||||
| TC035 | PASS | Sequential 4096-byte `dd` to exact EOF at size 262163; source/output SHA `056f8f75...3433`. | mount 0, md 0, output 0 |
|
||||
| TC036 | PASS | Independent `pread` start/mid/end ranges returned 10240/10240/24576 bytes at exact offsets; SHAs `5b6a742e...d858`, `29fa7799...849`, `6477f5a2...163d`. | mount 0, md 0, outputs 0 |
|
||||
| TC037 | PASS | 104857857-byte full `dd` took 61.44 s and matched SHA `1c8daa29...c661`; 5 MiB sample at 50 MiB matched SHA `f1b60e95...25c7`. | mount 0, md 0, outputs 0 |
|
||||
| TC038 | PASS | `readlink` returned exact `target.txt`; followed target source/mount SHA `887f519c...8ea3`. | mount 0, md 0, outputs 0 |
|
||||
| TC039 | PASS | Evidence size 73/layout 2; source and mounted long targets matched, output was 74 bytes including newline, followed file SHA `06539545...0f42`. | mount 0, md 0, outputs 0 |
|
||||
| TC040 | PASS | Source/mounted target was `/nonexistent/repo22-g1-target`; two follow attempts returned exact errno 2 at open. | mount 0, md 0, outputs 0 |
|
||||
| TC147 | PASS | Positive inline file SHA `0347f272...b01`; checksum-valid cross-block image returned errno 97 twice and truss `fstatat ERR#97`; dmesg `123->123`. | mounts 0, md units 0, outputs 0 |
|
||||
| TC150 | PASS | Current-KLD rerun with `feature=0x80 rootnid_2b=36 rootnid_8b=0 blocks_lo=17`; root inode 36, root SHA `04690aad...3105`, statfs blocks 17. | mount 0, md 0 |
|
||||
| TC151 | PASS | Current-KLD rerun: two direct opens returned errno 97, truss showed `fstatat ERR#97`, both mmap helpers exited 1 at open with integrity failure; dmesg `123->123`. | mount 0, md 0, outputs 0 |
|
||||
|
||||
## Outcome
|
||||
|
||||
| Result | Count |
|
||||
|---|---:|
|
||||
| PASS | 32 |
|
||||
| KERNEL-FAIL | 0 |
|
||||
| SHELVED/ISSUE | 0 |
|
||||
| ENVIRONMENT-UNAVAILABLE | 0 |
|
||||
|
||||
No G1 issue file was created. Existing issue files concern other groups or
|
||||
previously documented limitations and were not changed by this run.
|
||||
|
||||
## Discarded Attempts
|
||||
|
||||
- TC013 initially asserted a nonportable `stat(1)` wrapper exit status. The
|
||||
qualified run used two direct syscall probes and truss errno instead.
|
||||
- TC026 first exposed an invalid transformer assumption: a NUL had been added
|
||||
after non-trailing `..`. That image was discarded, the transformer was fixed,
|
||||
independent 1.9.3 validation and two-build reproduction passed, and TC026 was
|
||||
rerun with the corrected image.
|
||||
- TC037's first long-running remote stream did not preserve its final output.
|
||||
It was not counted; the complete qualified rerun retained elapsed time, both
|
||||
hashes, and cleanup evidence.
|
||||
- TC151 had two pre-mount/post-evidence command assertions with incorrect grep
|
||||
keys. Neither was counted; the complete qualified rerun used syscall errno and
|
||||
per-output integrity counts.
|
||||
|
||||
## Final Cleanup
|
||||
|
||||
Before final unload, `kldstat` showed ID 20 and the exact KLD SHA256 above.
|
||||
After `kldunload erofs`, the guest reported:
|
||||
|
||||
```text
|
||||
erofs_mounts=0 md_units=0 kld_present=0
|
||||
```
|
||||
|
||||
Generated fixtures, build objects, and native probes remain only in `/work/build`
|
||||
and guest `/tmp`; none is staged for the repository.
|
||||
@@ -0,0 +1,269 @@
|
||||
# repo22 G2 Full Manual Regression Report
|
||||
|
||||
Started: 2026-08-09 06:07 UTC
|
||||
|
||||
Completed: 2026-08-09 07:10 UTC
|
||||
|
||||
Baseline: `cd0e985b5ac54a4b7acb7042422329ad1729fb3e`
|
||||
|
||||
Branch: `manual-g2-20260809T060709Z`
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG, isolated port 9223
|
||||
|
||||
Overlay: `/work/build/repo22-manual-g2-20260809T060709Z-freebsd15-overlay.qcow2`
|
||||
|
||||
## Scope and Result
|
||||
|
||||
The exact assigned set was executed manually from the numbered Markdown
|
||||
procedures. No CI, runner, result wrapper, or unassigned TC was used.
|
||||
|
||||
| Test | Result | Primary evidence |
|
||||
|---|---|---|
|
||||
| TC002 | PASS | Real CRC32C ranges; valid mount; bad covered byte `ERR#97` |
|
||||
| TC008 | PASS | Bad magic `ERR#22`; 1023-byte provider `ERR#6`; empty md `EINVAL` |
|
||||
| TC010 | PASS | Qualified 16 TiB sparse provider; exact 64-bit `df` total |
|
||||
| TC015 | PASS | Bad block size/root/feature returned 22/97/45 |
|
||||
| TC016 | PASS | Checksum-field-only mutation returned `ERR#97` |
|
||||
| TC017 | PASS | Raw union fields; short `ERR#6`; qualified sparse mount |
|
||||
| TC018 | PASS | Exact read at physical offset 17592191561728 |
|
||||
| TC112 | PASS | Bad magic field returned `ERR#22` before root load |
|
||||
| TC113 | PASS | Resolved inode `openat` returned `ERR#45` |
|
||||
| TC114 | PASS | OOB FLAT_PLAIN `read` returned `ERR#97` |
|
||||
| TC115 | PASS | Future algorithm bit returned `ERR#45` |
|
||||
| TC116 | PASS | FBT decompressor entry count 1; `read` returned `ERR#5` |
|
||||
| TC119 | PASS | FLAT_PLAIN changed byte/hash returned without kernel error |
|
||||
|
||||
Count: 13 assigned, 13 PASS, 0 KERNEL-FAIL, 0 SHELVED, 0 ENV,
|
||||
0 duplicate, 0 omitted, 0 extra.
|
||||
|
||||
## Build and Guest
|
||||
|
||||
Both configurations were built natively in the isolated guest with kernel
|
||||
`-Werror`, `FREEBSD_SRC=/tmp/repo22-freebsd15-src`, and FreeBSD source
|
||||
`15.0-RELEASE-p9`. The running guest was `15.0-RELEASE-p8`.
|
||||
|
||||
| Configuration | KLD SHA256 | Result |
|
||||
|---|---|---|
|
||||
| `WITH_ZSTDIO=0` | `482e9c072c949f0459c422aa1efd9b35a2d99cf90b25bb01e59e00b440b6d0fe` | PASS |
|
||||
| `WITH_ZSTDIO=1` | `8349c97c7ced253fff32a9e706f29313f309cb63a270e4e39a4501426f2b95c6` | PASS |
|
||||
|
||||
The ZSTDIO KLD was loaded as ID 5 and was the only repo22 test KLD. It had the
|
||||
expected FreeBSD `ZSTD_*` references and no unresolved `bcmp`. The disabled KLD
|
||||
had neither `ZSTD_*` nor `bcmp` unresolved symbols.
|
||||
|
||||
## Fixture Qualification
|
||||
|
||||
`tests/prepare_error_fixtures.sh` ran twice in independent empty host
|
||||
directories with erofs-utils 1.8.6. Both `SHA256SUMS` files were identical and
|
||||
self-verified. Every field mutation records its exact field/path and old/new
|
||||
value. Same-size corruption variants preserve provider length; short and empty
|
||||
providers record their intentionally different lengths.
|
||||
|
||||
The helper independently proved the 4096-byte-block checksum forms:
|
||||
|
||||
```text
|
||||
canonical: field 1028 cleared, CRC32C [1024,4096), initial 0xffffffff
|
||||
kernel: CRC32C [1032,4096), seed 0x5045b54a
|
||||
valid control: stored=canonical=kernel=0xf7429681
|
||||
```
|
||||
|
||||
Raw transcripts, build logs, KLDs, guest logs, fixture trees, and the overlay
|
||||
remain untracked under `/work/build/repo22-manual-g2-20260809T060709Z*`.
|
||||
|
||||
## TC002
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: helper `inspect`; `sha256`; dynamic `mdconfig`; valid `mount`/`cmp`;
|
||||
direct negative `mount`; independent `truss`.
|
||||
|
||||
Hashes: valid `eb97860671931c76a171d13caa70ddc2c9731c6cdef1e99b9a3deb580baf70ae`;
|
||||
bad CRC `3f684e8cb03e3cb20a1920dccf23d9b7b19bc09d5745be04608e195c541ebf73`.
|
||||
Both were 7622656 bytes. The corruption changed byte 1088 without recomputing
|
||||
CRC; calculated checksum became `0x202160ab` while stored remained `0xf7429681`.
|
||||
Valid data matched; bad direct mount rc was 1 and `nmount` returned errno 97.
|
||||
Cleanup: mount absent and exact md detached.
|
||||
|
||||
## TC008
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: `wc -c`, `sha256`, dynamic `mdconfig`, direct `mount`, and `truss`
|
||||
for each attachable provider.
|
||||
|
||||
Hashes: bad magic `d1bc3aec03c58dae1b74b54d895c0a72e5b3088f05907ab2f6a0cb8dc38a615f`;
|
||||
1023-byte provider `5724796860baa23469b3118eff2567c96a0b64b7dadaf99eafb7ba3c65b9aa56`;
|
||||
empty provider `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.
|
||||
Bad magic returned errno 22. The 1023-byte md attached and mount returned errno
|
||||
6. The zero-byte provider was rejected by `mdconfig` with `EINVAL`. Cleanup:
|
||||
zero mounts and md units after all three subcases.
|
||||
|
||||
## TC010
|
||||
|
||||
Result: **PASS**, not SHELVED.
|
||||
|
||||
Commands: copy qualified prefix, `truncate -s 17592193667072`, `stat`, dynamic
|
||||
`mdconfig`, `diskinfo`, `mount`, `df -kT`, `df -iT`, and `cmp`.
|
||||
|
||||
Prefix hash: `d3ffadc6fc9e73f85a8a5973b4ac5ee2a2da57b4e8baeccd259fa4325a2146cf`.
|
||||
Fields were `blocks_hi=1`, `blocks_lo=1861`, `rootnid_8b=36`, and
|
||||
`total_blocks=4294969157`. The sparse file allocated 15232 UFS blocks while
|
||||
GEOM reported all 17592193667072 bytes. `df -k` total and Used were exactly
|
||||
17179876628; Avail was zero. Cleanup: unmounted, md detached, sparse provider
|
||||
removed. The former environmental issue is now RESOLVED.
|
||||
|
||||
## TC015
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: evidence/hash/length checks; three dynamic md attachments; direct
|
||||
mount status followed by independent `truss` errno capture.
|
||||
|
||||
Hashes: bad block size `b5c52c44ace6f693e39161102acde157efec2fce34eb4fef198681dfc7942b58`;
|
||||
bad root `9cdd61eb99b2e7f45d2505b3b95d6427f8f636e117b5ffabed064a3f83997d55`;
|
||||
unknown feature `95d19a490c38657a8c579a10f1f12088ab43c1819aa9b37ae86b841bb26a592a`.
|
||||
All were 7622656 bytes with recomputed valid CRC. Direct mount rc was 1 for
|
||||
each; `nmount` returned 22, 97, and 45 respectively. Cleanup: zero mounts/md.
|
||||
|
||||
## TC016
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: field evidence, `sha256`, `wc -c`, direct mount, and `truss`.
|
||||
|
||||
Hash: `04249f6b7d9b7322c171f1727408130d23cb0b83709aad7a4b77b39358a8dfe6`.
|
||||
The 7622656-byte image changed only checksum `0xf7429681 -> 0xf7429680`.
|
||||
Direct mount rc was 1 and `nmount` returned errno 97. Cleanup: no mount/md.
|
||||
|
||||
## TC017
|
||||
|
||||
Result: **PASS** with both negative guard and positive dynamic coverage.
|
||||
|
||||
Commands: raw `od` at offsets 1038/1060/1136; small-prefix mount; sparse
|
||||
provider creation; `diskinfo`; positive mount; `cmp`; `df -kT`.
|
||||
|
||||
Hash: `d3ffadc6fc9e73f85a8a5973b4ac5ee2a2da57b4e8baeccd259fa4325a2146cf`.
|
||||
Raw values were high 1, low 1861, root 36. The short prefix returned errno 6.
|
||||
The qualified provider mounted and reported exactly 17179876628 one-KiB
|
||||
blocks. Cleanup: unmounted, detached, sparse provider removed.
|
||||
|
||||
## TC018
|
||||
|
||||
Result: **PASS** with real I/O above 16 TiB.
|
||||
|
||||
Commands: sparse provider creation; high-offset `dd`; two raw `read_probe`
|
||||
calls; low-decoy and high-source `cmp`/hash; dynamic md mount and mounted hash.
|
||||
|
||||
Prefix hash: `cfd86ed1e39528d958e32819282c7fd87191f80a25d9575078dad473f40563a8`.
|
||||
The inode encoded `startblk_hi=1`, low 1347, combined block 4294968643, and
|
||||
physical offset 17592191561728. The original low payload at 5517312 was zeroed:
|
||||
its hash was `6a4875ddaceaa91fb3369f0f6d962f77442daf1b1d97733457d12bcabdf79441`.
|
||||
Raw high and mounted hashes both matched source
|
||||
`25eb31e024bc60745d68a5f7318753951804c7858b576cb698a4260af4a334a3`.
|
||||
Cleanup: unmounted, detached, sparse provider and probe outputs removed.
|
||||
|
||||
## TC112
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: raw magic `od`, hash/length, direct mount, and `truss`.
|
||||
|
||||
Hash: `d1bc3aec03c58dae1b74b54d895c0a72e5b3088f05907ab2f6a0cb8dc38a615f`.
|
||||
Magic was `0x21444142`; provider length remained 7622656. The canonical CRC was
|
||||
invalid as required after changing magic, while the unchanged production
|
||||
suffix checksum still matched `0xf7429681`. Direct rc was 1 and `nmount`
|
||||
returned errno 22. Cleanup: no mount/md.
|
||||
|
||||
## TC113
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: structured path inspection; mount/control `cmp`; direct and trussed
|
||||
`read_probe expect-error ... 45`; second control `cmp`.
|
||||
|
||||
Hash: `28d972c4065927326607fcd51dabff81cc88a28e2343f6056e55d04418172814`.
|
||||
Resolved NID 52 at inode offset 1664 changed format `0x0001 -> 0x8001` with
|
||||
valid CRC and equal provider length. `openat` returned errno 45; unaffected data
|
||||
remained exact. Cleanup: clean unmount and md detach.
|
||||
|
||||
## TC114
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: path inspection; mount/control `cmp`; direct and trussed
|
||||
`read_probe expect-error ... 97`; second control `cmp`.
|
||||
|
||||
Hash: `2afcb9dde4c8eaf30bb2541d00be36f726f5cf151d887e0d999803b3993285ed`.
|
||||
NID 54 field offset 1744 changed start block `1349 -> 1861`, equal to declared
|
||||
blocks, with valid CRC and equal media size. Target `read` returned errno 97;
|
||||
control remained exact. Cleanup: clean unmount and md detach.
|
||||
|
||||
## TC115
|
||||
|
||||
Result: **PASS**.
|
||||
|
||||
Commands: raw field `od`, hash/length, direct mount, and `truss`.
|
||||
|
||||
Hash: `a8d7d9cf0abcf34a5cd2366059c0294e08fdd20d02ed3b5b21005ddf78c69029`.
|
||||
`available_compr_algs` at byte 1106 changed `0x0000 -> 0x8000`; CRC was
|
||||
recomputed and media remained 716800 bytes. Direct rc was 1 and `nmount`
|
||||
returned errno 45. Cleanup: no mount/md.
|
||||
|
||||
## TC116
|
||||
|
||||
Result: **PASS** with direct decompressor-path proof.
|
||||
|
||||
Commands: parsed `dump.erofs -e` evidence; failed userspace extraction record;
|
||||
mount/control `cmp`; FBT enumeration and count; direct/trussed `read_probe`.
|
||||
|
||||
Hash: `f442785ea7cdbdc3f64b2ff89ea04942460cbdc5bea5615cbed237029edda8d8`.
|
||||
The equal-length 716800-byte image changed 64 bytes at 4128, wholly inside the
|
||||
parsed first physical extent `[4096,69632)`. Mount succeeded, excluding the
|
||||
media-size precheck. `fbt:erofs-zstdio:z_erofs_decompress:entry` counted one
|
||||
call for the target command, and `read` returned errno 5. Control remained
|
||||
exact. Cleanup: unmounted/detached; `dtraceall` and dependencies unloaded.
|
||||
|
||||
## TC119
|
||||
|
||||
Result: **PASS** with the revised real integrity semantics.
|
||||
|
||||
Commands: path/field evidence; image/source hashes; mount; complete mounted
|
||||
hash; two one-byte `read_probe` calls; byte compare; control `cmp`.
|
||||
|
||||
Hash: `74b54f998481f9e55b30f2393ee780e3b46144157a54b4dc0fc0c044c160bb5a`.
|
||||
The equal-length image flipped `/plain.bin` file offset 257 at provider byte
|
||||
5525761 from `0x00` to `0x80`; the valid superblock CRC remained unchanged.
|
||||
Source hash was `5647f05ec18958947d32874eeb788fa396a05d0bab7c1b71f112ceb7e9b31eee`;
|
||||
mounted hash was `e98bb2acaecb24ab86112478bc8cc1e3668c127b449fe2e664dc4804f9477d81`.
|
||||
Reads succeeded, the target byte differed, and control matched. Cleanup: clean
|
||||
unmount and md detach.
|
||||
|
||||
## Harness Notes
|
||||
|
||||
Four incomplete attempts were discarded and are not PASS evidence:
|
||||
|
||||
1. TC002 repeated host Python inspection in a guest without Python and exited
|
||||
before md attachment.
|
||||
2. TC002 initially trusted `truss` wrapper status; the qualified run separated
|
||||
direct mount rc from syscall tracing because FreeBSD `truss -o` returned 0
|
||||
around a failing mount.
|
||||
3. TC008 had a stray patch marker after its second subcase; trap cleanup ran and
|
||||
the complete three-subcase command was repeated.
|
||||
4. TC010 initially asserted the `df` Used column was free blocks; the qualified
|
||||
run used the correct `f_bfree=0` interpretation and repeated all steps.
|
||||
|
||||
## Final Cleanup
|
||||
|
||||
Per-TC cleanup ended with zero EROFS mounts and zero md providers. Global dmesg
|
||||
changed from 102 to 104 lines; both new lines were the expected
|
||||
`md0: truncating fractional last sector by 511 bytes` from TC008. New panic,
|
||||
fatal trap, page fault, general-protection fault, and double-fault count was
|
||||
zero. The exact test KLD unloaded successfully, leaving zero EROFS modules.
|
||||
The guest shut down normally and QEMU PID 285696 exited after 12 seconds. Port
|
||||
9223 no longer has a VM process.
|
||||
|
||||
## Issues
|
||||
|
||||
No kernel issue was found and no TC remains SHELVED. The former TC010 provider
|
||||
issue is retained as a resolved record in
|
||||
`issues/TC010-48bit-statfs-large-provider.md`.
|
||||
@@ -0,0 +1,132 @@
|
||||
# repo22 G4 full manual regression report
|
||||
|
||||
## Scope and result
|
||||
|
||||
- Exact scope: TC005, TC067-TC083, TC117, TC134-TC140, TC142.
|
||||
- Count check: 27 requested, 27 executed, 0 duplicate, 0 omitted.
|
||||
- Result: **PASS 27, FAIL 0, SHELVE 0, NOT RUN 0**.
|
||||
- Kernel source baseline: `9ae22009f23a65320730072a780998e80aa9b728`.
|
||||
- No repo22 kernel source was changed during this regression.
|
||||
- No kernel failure was found, so no new `issues/` entry was required.
|
||||
|
||||
## Isolated environment
|
||||
|
||||
- Worktree: `/work/build/repo22-manual-g4-20260809T072432Z`.
|
||||
- Branch: `manual-g4-20260809T072432Z`.
|
||||
- Dedicated qcow2 overlay backed by the FreeBSD 15 development base.
|
||||
- Dedicated SSH forward: `127.0.0.1:9224`; port 9222 was not used.
|
||||
- Guest: FreeBSD `15.0-RELEASE-p8`, amd64, GENERIC,
|
||||
`releng/15.0-n281036-53054229dcb3`.
|
||||
- Final guest cleanup: zero matching mounts, zero md units, EROFS malloc
|
||||
active count 0, KLD unloaded, guest responsive, QEMU stopped.
|
||||
|
||||
## Exact KLD build
|
||||
|
||||
The module used only `repo-community/repo22/src` from the baseline worktree.
|
||||
It was built for `x86_64-unknown-freebsd15` with all 14 Makefile source files,
|
||||
FreeBSD `15.0 RELEASE-p9` headers, clang/LLD 19.1.7, and `WITH_ZSTDIO=0`.
|
||||
The guest is p8; both header and guest are the same FreeBSD 15 release KBI.
|
||||
|
||||
```text
|
||||
erofs.ko SHA256 = ee0b2e7c4ebf3602de8be8d47ddcfd021bc90a5265e7e9bf449bb9de2fbf4f04
|
||||
guest kldstat = erofs.ko, module erofs, id 507
|
||||
```
|
||||
|
||||
## Fixture provenance
|
||||
|
||||
Installed `mkfs.erofs`, `dump.erofs`, and `fsck.erofs` reported erofs-utils
|
||||
1.8.6. `tests/g4_fixtures.py` created both source trees without consuming old
|
||||
images, ran four deterministic mkfs commands, transformed structured fields,
|
||||
and reopened every result. A second fresh output directory produced identical
|
||||
source and image checksum files.
|
||||
|
||||
```text
|
||||
source inventory SHA256 = c48f72777b0a04514fcfc7ea9c4ed77089ab0ed4537eac9cb7430e1acafe3d0a
|
||||
metabox payload SHA256 = 50c5740b5fe5630ee919022d7ee1ce59d3e7d089c9d66e2baf82bf9de8233d40
|
||||
```
|
||||
|
||||
| Image | SHA256 |
|
||||
|---|---|
|
||||
| `basic.erofs` | `b375a3ca60de64b2c635b3ec45c9285c840e0a05bec105d5d8f68308300e7670` |
|
||||
| `prefix-primary.erofs` | `49fc472a26412d154df729d8b0079801b35e97af2eb7fc3fa226dc65ee1963f9` |
|
||||
| `metabox-plain.erofs` | `dd7b04097d1bfe283b65c95d759acd2176beb7cb0f1fe9d5ddbc0694b5acba9d` |
|
||||
| `metabox-compressed.erofs` | `682b9dc1e0b492d935c03deba2ab644c1f0bcbf8e1ec623c15fb4990d25fe584` |
|
||||
| `metabox-fragment.erofs` | `c93f4c281ed621519ced42a45cdb21b543047b7058dab31ad0def851cc19f4f1` |
|
||||
| `bad-inline-entry.erofs` | `f18693c880bac0a273f2497cb0f81c0c52e19edc3ad45354eb38a39624adafd2` |
|
||||
| `bad-shared-entry.erofs` | `875c27df56de2336fded3ef1db1c9fdc035db618096aa9269bfe70ea6f59c664` |
|
||||
| `bad-shared-declared-bounds.erofs` | `19fc69cbe21051de2cf1cc19454cf64cc4a0ece444c6607956e066c2d4d332b3` |
|
||||
| `bad-prefix-declared-bounds.erofs` | `d24b7a171aa28c3a46e3893749a4d08ab5d467c103f47aefcc0ca0f4e153d365` |
|
||||
| `bad-metabox-truncated-extension.erofs` | `0bad23b1ad77eb8f2ffee83f89b7ef91b1c49ba2a20661adca21cd9efce0911e` |
|
||||
| `bad-ishare-prefix-id.erofs` | `10357f28a11c2a8bbc329e01a414248415719ef088734a6ad75b934c0f65fc43` |
|
||||
| `bad-fragment-self-loop.erofs` | `3b61228041591716c650a6613de30b2d9abe8b01148a9ae513b7de4d9b958d78` |
|
||||
| `bad-fragment-range.erofs` | `432618f98a6e32351448120d8c694a4f31b45cb3078e08dad39b85fa01b47039` |
|
||||
| `bad-metabox-recursive-nid.erofs` | `ab28a5478f569eb8a59e59e94448a85918885fcd328dadc2557ba6222dcd5aaf` |
|
||||
| `bad-packed-recursive-nid.erofs` | `ad9a0ed1598c8a8e16911190c9042d58315fbcdc23af824ea106af55ffe6f2f4` |
|
||||
|
||||
The fragment positive self-check found carrier NID 46, compressed-full layout,
|
||||
size 32768, fragment header `0x8000000000000064` at image offset 9696,
|
||||
fragment offset 100, packed NID 48, and packed size 32868. Thus
|
||||
`100 + 32768 == 32868`. Shared entries were at metabox offsets 4096, 4128,
|
||||
and 4156; prefix records were at 8192 and 8224 with base indexes 1 and 4.
|
||||
|
||||
## FreeBSD namespace semantics
|
||||
|
||||
Linux `user.*` was queried through FreeBSD namespace `user` with the leading
|
||||
`user.` removed. Linux `trusted.*`, `security.*`, and ACL indexes were queried
|
||||
through namespace `system`. Trusted/security list names retained their full
|
||||
prefixes; ACL names were `posix_acl_access`/`posix_acl_default`. This is the
|
||||
FreeBSD extattr ABI and intentionally does not copy Linux `getfattr` commands.
|
||||
|
||||
`getextattr` does not reliably communicate every kernel failure through its
|
||||
process status, so errno evidence came from guest `truss`: `ENOATTR=87`,
|
||||
`EINTEGRITY=97`, and `EROFS=30`. Every corruption in this scope returned
|
||||
`EINTEGRITY`; no case required normalization to `EIO=5`.
|
||||
|
||||
## Per-TC results
|
||||
|
||||
| TC | Guest evidence | Result |
|
||||
|---|---|---|
|
||||
| TC005 | inline user list; exact NUL-containing value; miss `ENOATTR 87` | PASS |
|
||||
| TC067 | shared `shared_key` exact on two files; inline `local` exact | PASS |
|
||||
| TC068 | missing name and wrong namespace both `ENOATTR 87` | PASS |
|
||||
| TC069 | three shared names once; text, NUL, and binary values exact | PASS |
|
||||
| TC070 | shared `trusted.config` exact; unprivileged system access denied | PASS |
|
||||
| TC071 | shared `security.selinux` exact; miss `ENOATTR 87` | PASS |
|
||||
| TC072 | system lists trusted, security, ACL names; user subset empty | PASS |
|
||||
| TC073 | plain metabox mounted; bit-63 inodes, file hashes, xattrs exact | PASS |
|
||||
| TC074 | three inline user values exact, including `00..1f` | PASS |
|
||||
| TC075 | inline `trusted.admin` exact; user lookup `ENOATTR 87` | PASS |
|
||||
| TC076 | capability and SELinux binary bytes exact | PASS |
|
||||
| TC077 | packed user long-prefix names and values exact | PASS |
|
||||
| TC078 | packed trusted long-prefix names and values exact | PASS |
|
||||
| TC079 | packed NID 1201; two qualified records; both namespaces exact | PASS |
|
||||
| TC080 | four repeated prefix lookups exact; miss `ENOATTR 87` | PASS |
|
||||
| TC081 | metabox shared, inline, and long-prefix shared values exact | PASS |
|
||||
| TC082 | raw ACL bytes and `getfacl -n` agree; user lookup `ENOATTR 87` | PASS |
|
||||
| TC083 | four user attributes exact; system list empty | PASS |
|
||||
| TC117 | malformed inline/shared gets both `EINTEGRITY 97`; mount stable | PASS |
|
||||
| TC134 | nonzero xattr base shared value exact on two bit-63 inodes | PASS |
|
||||
| TC135 | metabox, packed, and primary prefix backings all exact | PASS |
|
||||
| TC136 | truncated extension and bad ishare ID mounts `EINTEGRITY 97` | PASS |
|
||||
| TC137 | shared get and prefix mount bounded at declared image, errno 97 | PASS |
|
||||
| TC138 | UID order 3002/2002 accepted; empty fallback; 3 negatives errno 97 | PASS |
|
||||
| TC139 | FIFO stat/ACL/xattr reads; set/delete operations `EROFS 30` | PASS |
|
||||
| TC140 | compressed and fragment metabox positive; 4 negatives errno 97 | PASS |
|
||||
| TC142 | independent fragment rerun positive; 4 negatives errno 97 | PASS |
|
||||
|
||||
## Stability and cleanup
|
||||
|
||||
Positive values were compared as `getextattr -qq -x` bytes, not display text.
|
||||
Negative mount and VOP calls were traced at the kernel syscall boundary. Each
|
||||
case recorded zero matching mounts and zero matching md providers after its
|
||||
cleanup. TC140 and TC142 additionally showed:
|
||||
|
||||
```text
|
||||
erofs active allocations = 0
|
||||
guest responsiveness = ok
|
||||
panic/trap/hang = none
|
||||
```
|
||||
|
||||
The final KLD unload succeeded. The dedicated guest powered off, QEMU exited,
|
||||
and TCP port 9224 no longer had a listener. Generated images, KLDs, overlays,
|
||||
and raw logs remained outside Git and were not included in the commit.
|
||||
@@ -0,0 +1,154 @@
|
||||
# repo22 G3 Full Manual Regression Report
|
||||
|
||||
Execution window: 2026-08-09 10:42-10:59 UTC
|
||||
|
||||
Exact source baseline: 9ae22009f23a65320730072a780998e80aa9b728
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG, port 9222
|
||||
|
||||
## Scope and Method
|
||||
|
||||
This run covers exactly these 31 test cases and no others:
|
||||
|
||||
TC041-TC066, TC141, TC148, TC149, TC152, TC153
|
||||
|
||||
Every test was executed directly from its numbered Markdown procedure with a
|
||||
fresh dynamic md provider. No CI, runner, result wrapper, historical PASS, or
|
||||
unassigned TC supplied a result.
|
||||
|
||||
Set audit: requested 31, selected 31, unique 31, duplicate 0, missing 0,
|
||||
extra 0.
|
||||
|
||||
## Exact Build
|
||||
|
||||
The source archive was exported with git archive from the exact baseline. Its
|
||||
SHA256 was:
|
||||
|
||||
c7f36610a523427dc406023e1c27fc87a03076bbde2e6b1406311fa5ddb06b0f
|
||||
|
||||
The module was built natively in the guest with:
|
||||
|
||||
FREEBSD_SRC=/tmp/repo22-freebsd15-src WITH_ZSTDIO=0 ./build.sh
|
||||
|
||||
The build completed with kernel -Werror. The KLD loaded as ID 20, size 0xb8a8,
|
||||
and remained the only loaded EROFS module for the test interval.
|
||||
|
||||
| Item | SHA256 |
|
||||
|---|---|
|
||||
| erofs.ko | 19ad086bd2508cbb4c57b1a51f38c93057d438b84fbcfa2e637ff2519f5bc590 |
|
||||
| readdir_probe | 2387964fd0c153f9cba0e41dd57fbdc1d48c2f2b779540f27387fb168cd35128 |
|
||||
| g3_vfs_probe | 08fe3999600dd1826805c00a17af3d893d247a593c0812f767d85a2633302068 |
|
||||
| stat_special | 1c9b4e8fea449d2fc7b985c468a0267448db5998dd413c6c4ed36a1ab0c78ea4 |
|
||||
| nfs_fh_tool | db704f67d718d04b84ed32b5cdbecb93a99c5fe3b4ab238d603a10e005d790e3 |
|
||||
| mmap_fault | ba32712f9a4a01f4d85d8f5f6fafe2741acbd6b185a85fa759da73dbc416233c |
|
||||
| sparse_hole_probe | e5e1d8750fb671f0fccafdf51b61f4b0e73837718906dbd5fd5e8ab01cc790aa |
|
||||
|
||||
The KLD unresolved-symbol list contained both
|
||||
vnode_pager_local_getpages and vnode_pager_local_getpages_async; both resolved
|
||||
at load.
|
||||
|
||||
## Fixture Qualification
|
||||
|
||||
tests/prepare_g3_fixtures.sh completed its structured assertions and all four
|
||||
checksum-manifest checks. The relevant image hashes are:
|
||||
|
||||
| Alias | Image | SHA256 |
|
||||
|---|---|---|
|
||||
| P | vfs-plain.erofs | 79f0b5f4aa8e7ea532b711ee8fb466f14f35d0952446d41ba4bbc4d6e008b8ff |
|
||||
| Z | vfs-lz4.erofs | 07e2d1fbda0e6dc1233e6943a20f85e1f6652b580632c4c8264641a9a8805743 |
|
||||
| N | namei-base.erofs | d1730ff23836797c6c09e1b39b1cf23efc16e27f85ab07fdcab577bb82871659 |
|
||||
| Pad | namei-padding-nonzero.erofs | 9a94e9af2cab264b9c11975a20d78e615d6fe1e6f86267173cb5ac86aecb2b17 |
|
||||
| Cshort | namei-corrupt-short.erofs | fb89f74795a5569ed3a85d63836dd75a06e1f17823d0508048c37da710ea6e75 |
|
||||
| Coff | namei-corrupt-nameoff.erofs | b0b70ee615f163430f04edb91ab76c27ee8cc9a75b8ebb2008834f5b550e3933 |
|
||||
| Cname | namei-corrupt-name.erofs | 6a980ad3e241603eda2ef71a470c82975291c614366a401e4e89c17c9adf9b91 |
|
||||
| SC | special-compact.erofs | fd78256dd83d9d6d957e5f843c7a8e8a175a4b3243d528bebd299b0226853237 |
|
||||
| SE | special-extended.erofs | e73e9b84d9ceb8c2b07e9c2732733b0fd607736c68c09522a2402fbeef6ba8d5 |
|
||||
| H | extent-hole-5g.erofs | 50014a24493918247e36511ad34a2fe8ab47ae09ea46d7fd62a1bed445a6c65f |
|
||||
| L | large-dir-intmax.erofs | 0f90d3d57adbbbd946e41b225c1f6c464915c6abb0b13478ec9b2a318def1f72 |
|
||||
| LS | TC153 sparse prefix | f9337f83b1f568f6d904331a7749e6362a691055cd5af95b59bc89772f04e3b0 |
|
||||
|
||||
The wide directory has 320 real files. The structured padding mutation records
|
||||
wide NID 42, directory block 6, 80 dirents in the patched block, final-name
|
||||
offset 4043, and the eight changed bytes at offsets 4084:4092.
|
||||
|
||||
The 5 GiB fixture records size 5368713216 and one 16-byte extent with plen=0.
|
||||
TC153 records extended FLAT_PLAIN/Layout 0, NID 40, start block 16,
|
||||
2147483649 directory blocks, and final block index 2147483648.
|
||||
|
||||
## Per-Test Results
|
||||
|
||||
Every cleanup cell means the literal umount and mdconfig detach commands
|
||||
returned zero; a following mount -t erofs and mdconfig -l produced no rows.
|
||||
|
||||
| Test | Result | Actual command, errno/behavior, and hash evidence | Cleanup |
|
||||
|---|---|---|---|
|
||||
| TC041 | PASS | readdir_probe testdir 512; errno 0; 36 entries/restarts, types 8 dir/24 reg/4 link, FNV a3b969b5aa90cdbe, exact 34-name manifest and 255-byte name; image P. | umount 0; md 0 |
|
||||
| TC042 | PASS | readdir_probe wide 128 plus three stat calls; errno 0; 322 entries/restarts, 320 files, FNV f3bfa2c15b231a59, sample NIDs 72/390/710; image N. | umount 0; md 0 |
|
||||
| TC043 | PASS | four g3_vfs_probe stat calls, cmp, sha256; errno 0; regular/dir/link metadata stable and mounted file matched source; image P. | umount 0; md 0 |
|
||||
| TC044 | PASS | direct stat probes; missing/missing-parent errno 2, positive errno 0; image P. | umount 0; md 0 |
|
||||
| TC045 | PASS | stat four case variants plus direct miss; NIDs 101/88/86/87, unmatched spelling errno 2; image P. | umount 0; md 0 |
|
||||
| TC046 | PASS | readdir_probe testdir 4096 and exact manifest cmp; errno 0; 36 entries/restarts, FNV a3b969b5aa90cdbe; image P. | umount 0; md 0 |
|
||||
| TC047 | PASS | readdir_probe wide 128, sorted count/duplicate checks; errno 0; 322 entries/restarts and 320 unique files; image N. | umount 0; md 0 |
|
||||
| TC048 | PASS | readdir_probe wide 128; every kernel d_off reopened/lseeked, every libc cookie seeked on its producing DIR stream; 322/322 restarts, FNV f3bfa2c15b231a59; image N. | umount 0; md 0 |
|
||||
| TC049 | PASS | readdir_probe plus two dot stat calls and cmp; errno 0; dot and directory both NID 44, source-exact path; image P. | umount 0; md 0 |
|
||||
| TC050 | PASS | readdir_probe child plus parent/dotdot stat and cmp; errno 0; parent NID stable and multiple dotdot path source-exact; image P. | umount 0; md 0 |
|
||||
| TC051 | PASS | repeated stat and cmp; errno 0; byte-identical NID/mode/size/generation and data; behavior-level only; image P. | umount 0; md 0 |
|
||||
| TC052 | PASS | repeated negative stat then positive stat; errno 2/2/0; no positive poisoning; behavior-level only; image P. | umount 0; md 0 |
|
||||
| TC053 | PASS | repeated stat, getfh, compare, describe; errno 0; NID 101, gen 3444757833, identical handle SHA 5000984ddaeca26d90e8fd59568a55afcb074f51aaa00eed8d422d698a4aefc6; image P. | umount 0; md 0 |
|
||||
| TC054 | PASS | parent and two dotdot stat calls; errno 0; identical NID/generation/type, no lock diagnostic; behavior-level only; image P. | umount 0; md 0 |
|
||||
| TC055 | PASS | g3_vfs_probe stat, native stat, sha256 on plain/LZ4; errno 0; size 21211, blocks 48/8, data SHA d09ed1cee6520e36e54d5f2fd8c3bc74bd46cd8a58426744cc301b638c83e9d4; images P/Z. | two umount 0; md 0 |
|
||||
| TC056 | PASS | stat_special char/block/fifo plus stat on compact/extended images; errno 0; rdev 2748:344865, FIFO NODEV, size/blocks 0; images SC/SE. | two umount 0; md 0 |
|
||||
| TC057 | PASS | nobody direct access-read, access-exec directory/file, open-read; errno 0 for all; image P. | umount 0; md 0 |
|
||||
| TC058 | PASS | nobody direct access probes; restricted read/read/exec errno 13, regular-file write access errno 30; image P. | umount 0; md 0 |
|
||||
| TC059 | PASS | four readlink probes, cmp, broken follow; target lengths 8/16/14/119 and FNV hashes recorded, follow errno 0/2; image P. | umount 0; md 0 |
|
||||
| TC060 | KERNEL-FAIL | g3_vfs_probe pathconf; values 255/1024/64/2147483647, but NO_TRUNC and CHOWN_RESTRICTED each errno 22; probe status 1; image P; issue TC060-pathconf-standard-values.md. | umount 0; md 0 |
|
||||
| TC061 | PASS | direct chmod probe and before/after stat cmp; errno 30, metadata unchanged; image P. | umount 0; md 0 |
|
||||
| TC062 | PASS | direct chown probe and before/after stat cmp; errno 30, metadata unchanged; image P. | umount 0; md 0 |
|
||||
| TC063 | PASS | direct O_RDWR/truncate/create probes plus before/after sha256; errno 30/30/30, hash unchanged and no new entry; image P. | umount 0; md 0 |
|
||||
| TC064 | PASS | nm -u plus mmap_fault on plain/LZ4; helper errno assertions passed; size 21211, six faults, FNV a1890a1c216724be, EOF/SIGBUS/COW PASS; images P/Z. | two umount 0; md 0 |
|
||||
| TC065 | PASS | real mmap_fault on plain/LZ4; MAP_SHARED EACCES and O_RDWR EROFS asserted, same FNV a1890a1c216724be; images P/Z. | two umount 0; md 0 |
|
||||
| TC066 | PASS | exact source erofs_bmap EOPNOTSUPP audit, KLD symbols, real mmap fallback on plain/LZ4; both FNV a1890a1c216724be, no strategy diagnostic; images P/Z. | two umount 0; md 0 |
|
||||
| TC141 | PASS | fresh open-read/cmp, two ENOENT lookups, 322-cookie probe on N/Pad; two lookup and readdir attempts on Cshort/Coff/Cname all errno 97; all five hashes above. | five umount 0; md 0 |
|
||||
| TC148 | PASS | actual Pad image cmp/count/readdir_probe; errno 0; 320 files, 322 restarts, FNV f3bfa2c15b231a59; hash Pad and patch offsets recorded. | umount 0; md 0 |
|
||||
| TC149 | PASS | real mmap_fault on plain/LZ4; same size/fault/FNV/EOF/SIGBUS/COW evidence as TC064 with fresh mounts; images P/Z. | two umount 0; md 0 |
|
||||
| TC152 | PASS | stat, vmstat -m, two timed sparse_hole_probe calls at 3221225472; errno 0, pread/mmap zero, 0.02 s each, RSS 2652/2656 KiB, erofs memory 768 bytes; image H. | umount 0; md 0 |
|
||||
| TC153 | PASS | 8796093091840-byte sparse provider, diskinfo, stat, two cold and one post-readdir lookup; /huge size 8796093026304, all lookup errno 97, root 3-cookie FNV cd20cb6ae9fe01ba; prefix LS/KLD hashes. | umount 0; md 0; sparse file removed |
|
||||
|
||||
## Outcome
|
||||
|
||||
| Result | Count |
|
||||
|---|---:|
|
||||
| PASS | 30 |
|
||||
| KERNEL-FAIL | 1 |
|
||||
| SHELVED/ISSUE | 0 |
|
||||
| ENVIRONMENT-UNAVAILABLE | 0 |
|
||||
|
||||
The only new open issue is issues/TC060-pathconf-standard-values.md. The former
|
||||
TC153 validation issue is resolved by the qualified sparse-provider run.
|
||||
|
||||
## Discarded Attempts
|
||||
|
||||
- The first TC041 helper run passed telldir cookies across a closed and newly
|
||||
opened DIR stream. POSIX only guarantees a cookie on its producing stream.
|
||||
The helper was corrected and rebuilt with -Werror; this run was not counted.
|
||||
- TC041 was then attempted with a 128-byte getdirentries buffer, too small for
|
||||
the fixture's 255-byte name record. The 512-byte qualified rerun passed.
|
||||
- TC047 was first attempted with a 64-byte buffer, too small for its long
|
||||
records. The 128-byte qualified rerun still forced repeated reads and passed.
|
||||
|
||||
No discarded command was counted as a kernel result.
|
||||
|
||||
## Dmesg and Final Cleanup
|
||||
|
||||
The dmesg baseline had 123 lines. The only eight new lines were expected
|
||||
mmap_fault child exits on SIGBUS: two images for each of TC064, TC065, TC066,
|
||||
and TC149. There was no new EROFS warning, integrity diagnostic, assertion,
|
||||
trap, panic, OOM, dirty writeback, or "No strategy for buffer" line.
|
||||
|
||||
Before unload, the exact KLD hash was rechecked. KLD unload returned zero.
|
||||
The guest then reported:
|
||||
|
||||
erofs_mounts=0 md_units=0 kld_present=0 guest_artifacts=0
|
||||
|
||||
The TC153 sparse provider and all transferred/generated guest files were
|
||||
removed.
|
||||
@@ -0,0 +1,157 @@
|
||||
# repo22 TC060 pathconf fix manual test report
|
||||
|
||||
Execution window: 2026-08-09 11:20-11:35 UTC
|
||||
|
||||
Exact source commit: `cdba7e54fb9e9d82980a06f178e68d0bbc1663ac`
|
||||
|
||||
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG, port 9222
|
||||
|
||||
## Scope and Result
|
||||
|
||||
TC060 is **PASS**. The original six-key helper returned the required values
|
||||
with errno 0, compatibility queries retained their previous values, an unknown
|
||||
name returned EINVAL, mount/read/read-only smoke passed, dmesg did not change,
|
||||
and final mount/md/KLD/artifact counts were zero.
|
||||
|
||||
This was a direct manual run. No CI, result wrapper, or historical PASS supplied
|
||||
the result. `WITH_ZSTDIO=1` was build-verified; runtime TC060 used
|
||||
`WITH_ZSTDIO=0` because pathconf is compression-independent and this avoids an
|
||||
unnecessary optional runtime dependency.
|
||||
|
||||
## Implementation Basis
|
||||
|
||||
FreeBSD 15 `sys/kern/vfs_default.c` shows that `vop_stdpathconf()` handles
|
||||
generic `_PC_ASYNC_IO`, `_PC_PATH_MAX`, and several zero-valued optional
|
||||
features, then returns EINVAL for other names. The same source tree's UFS,
|
||||
tmpfs, and ext2fs vnode operations explicitly return 1 for
|
||||
`_PC_CHOWN_RESTRICTED` and `_PC_NO_TRUNC` before delegating the remaining names
|
||||
to `vop_stdpathconf()`.
|
||||
|
||||
The EROFS fix adds only those two switch labels and retains the existing
|
||||
default delegation. `src/namei.c` rejects components longer than
|
||||
`EROFS_NAME_LEN` with ENAMETOOLONG, while `src/erofs_vnops.c` rejects uid/gid
|
||||
mutation with EROFS. Linux EROFS's 255-byte name limit and ENAMETOOLONG lookup
|
||||
check were reviewed only for maintenance similarity; FreeBSD VOP behavior was
|
||||
authoritative.
|
||||
|
||||
## Exact Build
|
||||
|
||||
The repo22 subtree was exported directly from the exact source commit. Later
|
||||
`current/`-only reporting changes did not alter `src/`.
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Source archive SHA256 | `a4c871ca7cefc49470ef0003f876792380a8d8db6cbeaefb96456d06ad946d53` |
|
||||
| FreeBSD source path | `/tmp/repo22-freebsd15-src` |
|
||||
| Compiler | FreeBSD clang 19.1.7 |
|
||||
| Build command | `FREEBSD_SRC=/tmp/repo22-freebsd15-src WITH_ZSTDIO=N ./build.sh` |
|
||||
|
||||
Both clean-object builds completed with kernel `-Werror`:
|
||||
|
||||
| Configuration | Module SHA256 | Size | Undefined-symbol audit | Result |
|
||||
|---|---|---:|---|---|
|
||||
| `WITH_ZSTDIO=0` | `68536e03ce93c6c04aab9cf29dab81ee5802d4b94401bd1a4bd752de40c0e504` | 73896 | no `bcmp`, no `ZSTD_*` | PASS |
|
||||
| `WITH_ZSTDIO=1` | `598f171d355af78c64407a6d513d20f053530620da92df7f8ccfdf9a804e463d` | 78728 | no `bcmp`; only five expected FreeBSD `ZSTD_*` APIs | PASS |
|
||||
|
||||
The enabled module referenced `ZSTD_DCtx_setParameter`,
|
||||
`ZSTD_createDCtx_advanced`, `ZSTD_decompressStream`, `ZSTD_freeDCtx`, and
|
||||
`ZSTD_isError`.
|
||||
|
||||
## Fixture and Probes
|
||||
|
||||
| Artifact | SHA256 |
|
||||
|---|---|
|
||||
| `vfs-plain.erofs` | `79f0b5f4aa8e7ea532b711ee8fb466f14f35d0952446d41ba4bbc4d6e008b8ff` |
|
||||
| tracked `g3_vfs_probe` binary | `08fe3999600dd1826805c00a17af3d893d247a593c0812f767d85a2633302068` |
|
||||
| one-run `pathconf_audit` source | `c2cc69297848cad8c806d47585ef670ae5b378827450110e2cf47cf82ed527b3` |
|
||||
| one-run `pathconf_audit` binary | `17aa9d738871ac424900ed7a928cb72cc2e5a288f2c2f9744906fa139a78835b` |
|
||||
|
||||
Both probes compiled natively with `-O2 -Wall -Wextra -Werror -std=c17`.
|
||||
The one-run helper was kept outside the repository and only printed each
|
||||
value/errno plus assertions for unknown-name and overlong-component behavior.
|
||||
|
||||
## TC060 Values
|
||||
|
||||
The exact original command was:
|
||||
|
||||
```text
|
||||
./g3_vfs_probe pathconf /tmp/repo22-tc060-fix/mnt/testdir
|
||||
```
|
||||
|
||||
It exited 0 and printed:
|
||||
|
||||
```text
|
||||
name_max=255 path_max=1024 filesizebits=64 link_max=2147483647
|
||||
no_trunc=1 chown_restricted=1
|
||||
```
|
||||
|
||||
The expanded value/errno capture was:
|
||||
|
||||
| Query | Actual value | errno |
|
||||
|---|---:|---:|
|
||||
| `_PC_NAME_MAX` | 255 | 0 |
|
||||
| `_PC_PATH_MAX` | 1024 | 0 |
|
||||
| `_PC_FILESIZEBITS` | 64 | 0 |
|
||||
| `_PC_LINK_MAX` | 2147483647 | 0 |
|
||||
| `_PC_NO_TRUNC` | 1 | 0 |
|
||||
| `_PC_CHOWN_RESTRICTED` | 1 | 0 |
|
||||
| `_PC_ASYNC_IO` | 200112 | 0 |
|
||||
| `_PC_ACL_EXTENDED` | 1 | 0 |
|
||||
| `_PC_ACL_PATH_MAX` | 254 | 0 |
|
||||
| `_PC_ACL_NFS4` | 0 | 0 |
|
||||
| unknown name `INT_MAX` | -1 | 22 (`EINVAL`) |
|
||||
|
||||
A lookup using a 256-byte path component returned -1 with errno 63
|
||||
(`ENAMETOOLONG`), confirming the reported no-truncation behavior.
|
||||
|
||||
## Mount and Read Smoke
|
||||
|
||||
The module loaded as KLD file ID 20, module `erofs`, and the fixture attached
|
||||
as dynamic `md0`. The mount line was:
|
||||
|
||||
```text
|
||||
/dev/md0 on /tmp/repo22-tc060-fix/mnt (erofs, local, read-only, acls)
|
||||
```
|
||||
|
||||
`testdir` reported inode 44, mode `drwxr-xr-x`, and size 1039. Reading
|
||||
`testdir/file.txt` produced `repo22 G3 file payload` and SHA256
|
||||
`523af4c899ba3b8f4fa4d854fe609590be271905b63932cf067ca9630e612687`.
|
||||
Opening the same file with `O_RDWR` returned errno 30 (`EROFS`) as expected.
|
||||
|
||||
## Dmesg and Cleanup
|
||||
|
||||
The qualified run's dmesg had 131 lines before and after, with identical
|
||||
SHA256 `91bf74a3607f432b4802f143bcf9d050c2f9e741da0a54f3e6a1b47bcc996a06`.
|
||||
There was no new EROFS diagnostic, assertion, trap, panic, or resource warning.
|
||||
|
||||
Qualified cleanup returned zero for umount, md detach, and KLD unload. After
|
||||
removing the transferred archive and test directory, the final guest audit was:
|
||||
|
||||
```text
|
||||
erofs_mounts=0
|
||||
md_units=
|
||||
erofs_klds=0
|
||||
guest_artifacts=0
|
||||
```
|
||||
|
||||
## Discarded Attempt
|
||||
|
||||
The first otherwise successful runtime pass loaded the KLD from the
|
||||
non-canonical filename `erofs-zstdio0.ko`. Its explicit `kldunload erofs`
|
||||
command could not resolve that filename, so the run stopped before final dmesg
|
||||
and cleanup qualification. Its mount and md were already clean; KLD file ID 20
|
||||
was then unloaded directly and the guest returned to zero resources. The full
|
||||
test was repeated with canonical `erofs.ko`, and only that rerun is counted.
|
||||
|
||||
## Independent Review
|
||||
|
||||
The post-test review found no lock, resource, or error-path issue:
|
||||
|
||||
- `VOP_PATHCONF` is called with the vnode shared-locked; the function does not
|
||||
change lock state.
|
||||
- The new constant-return branches allocate nothing and acquire no references.
|
||||
- Both normal and FIFO vnode vectors use the same filesystem-wide result.
|
||||
- Existing value branches are unchanged, and unsupported names still reach
|
||||
`vop_stdpathconf()` and return EINVAL.
|
||||
- The syscall copies `retval` only on error 0, so the unknown-name error path
|
||||
cannot expose a stale value.
|
||||
@@ -0,0 +1,212 @@
|
||||
# repo22 G5 compression full manual regression report
|
||||
|
||||
## Scope and result
|
||||
|
||||
- Exact scope: TC003, TC004, TC084-TC092, TC102-TC110, TC143-TC146.
|
||||
- Count check: 24 requested, 24 executed, 0 duplicate, 0 omitted.
|
||||
- Result: **PASS 23, PARTIAL 1, FAIL 0, NOT RUN 0**.
|
||||
- SHELVED subscenario: TC146 explicit mapped extent only.
|
||||
- TC146 overall: **PARTIAL**; HEAD2 PASS, interlaced PASS, explicit extent
|
||||
SHELVED.
|
||||
- Kernel baseline: `f383bbbbff301a6bde18894f03ab88a8c0cc885a`.
|
||||
- Initial and pre-push `xdm/main` baseline: the same commit.
|
||||
- No repo22 kernel source was changed. No kernel failure issue was opened.
|
||||
|
||||
## Isolated environment
|
||||
|
||||
- Worktree: `/work/build/repo22-manual-g5-20260809T085450Z`.
|
||||
- Branch: `manual-g5-20260809T085450Z`.
|
||||
- Artifact root: `/work/build/repo22-g5-20260809T085450Z`.
|
||||
- Dedicated qcow2 overlay backed by the FreeBSD development base.
|
||||
- Dedicated SSH forward: `127.0.0.1:9225`.
|
||||
- Guest: FreeBSD `15.0-RELEASE-p8`, amd64,
|
||||
`releng/15.0-n281036-53054229dcb3`.
|
||||
- Guest toolchain: FreeBSD clang/LLD 19.1.7.
|
||||
- Host tools: mkfs/dump/fsck erofs-utils 1.8.6.
|
||||
- No CI, runner, or wrapper was used. Each Markdown test was issued manually.
|
||||
|
||||
## Reproducible fixture
|
||||
|
||||
`tests/g5_fixtures.py` generated source content, invoked mkfs with fixed UUID,
|
||||
timestamp, owner, sort order, and one worker, transformed structured records,
|
||||
reopened every image, and wrote a field manifest. Two final fresh output
|
||||
directories produced byte-identical source and image checksum inventories.
|
||||
|
||||
```text
|
||||
SHA256SUMS SHA256 = 8535696f187f818df8b3b30ee35cd8837aceadef9de7a85be1475732dcdf0596
|
||||
SOURCE-SHA256SUMS SHA256 = 145bd663d7552025b486ec15c0b4c1c0ce99affa7728dc4d762134bdc92420c3
|
||||
fixture manifest SHA256 = 63ecdb46f8cc3c04e348e24e0f1306ad5e4606bf1fa108919f946515d7b41d63
|
||||
image count = 25
|
||||
source count = 19
|
||||
guest hash verification = 25 images, 19 sources
|
||||
```
|
||||
|
||||
### Image hashes
|
||||
|
||||
| Image | SHA256 |
|
||||
|---|---|
|
||||
| `deflate-level1.erofs` | `58dc78da2ec5a751688f53c4eee34f94b7f828d68b7225aea006d08374187bfe` |
|
||||
| `deflate-level6.erofs` | `4a3b5d2ce6b8d1bad23aa2c4d4ce38f83bc0e6642523e9165e58afe03e030c28` |
|
||||
| `deflate-level9.erofs` | `c4bf219a5130d475c26452a1a5346c63ed1f434afd4315972ee30ebc66f9c751` |
|
||||
| `deflate-partial-ref.erofs` | `fccb2f4038dca8b7277984e255a304bb6eee5de51fedd7b7261ef4f879362c81` |
|
||||
| `deflate-partial-ref-corrupt.erofs` | `063e1bc07d2c3a3bf3b17c0cce0237eca7000eb16abdf8a1b4428d8cc7880811` |
|
||||
| `extent-attempt.erofs` | `ee7472c23ccffd5eef6f4a3171ea53ae2e840f8a1ea417b2630065175ecf3225` |
|
||||
| `head2.erofs` | `4948335059e605ff70da4f6e516c4ba24d58313d10e836cc6fe9c9c468775a23` |
|
||||
| `head2-corrupt.erofs` | `7c25e9c8c767d2717c06b1efc854f0bb3b1f370cf3e2a8e2d53e767918bfe50f` |
|
||||
| `interlaced.erofs` | `7b77055dfae301a0acc3202cf0d7cf7062eb82c6101d16b68c91f70615e1638b` |
|
||||
| `lz4-compact-4k.erofs` | `0701a37430372f1240ad6485c83f0048d97bb49ae1ca85e1423dd05101bf5578` |
|
||||
| `lz4-compact-64k.erofs` | `967cc1b625546f9f9f881472e71cc3d849c65feb4e98e67fbfdc9b90a4be6dda` |
|
||||
| `lz4-compact-256k.erofs` | `6fbded24756b557c7ce19a5936dcde01fb75d4b1c91b0236fca46ee0c6331db0` |
|
||||
| `lz4-full-4k.erofs` | `3bc6eb538752673e2b9f905a0382887bccf417574a0d04901932cfeaac258bc7` |
|
||||
| `lz4-large.erofs` | `0340026fe5fea5fd7c6c4c03f288815bc86c7517c2c2a9a2ca21c61dfb96a973` |
|
||||
| `lz4-ztail.erofs` | `18cd045d7f2b8e542a0dda6fb0063c266919928e6e485f498ed93c8992d9ae3a` |
|
||||
| `lzma-level6.erofs` | `32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9` |
|
||||
| `lzma-large.erofs` | `6534870a686c3c5ba58fc665ea4995c07427a15c9d5ac7747690114138adf47d` |
|
||||
| `lzma-partial-ref.erofs` | `fbbcbcb8a178370a8f7665c3e0cff8df5bb6f81c3ba38a0e44c137e8ed165402` |
|
||||
| `lzma-partial-ref-corrupt.erofs` | `23981af2f4e36f88e382fff975f3c4fb00cb4b02c0f40f10948602ab4ce39f23` |
|
||||
| `microlzma-edge.erofs` | `27a19758141d64430d7536774fadf34ba7f8befa0bc36442b789e494b4b37d4c` |
|
||||
| `zstd-level1.erofs` | `34f51d5b1273cc3fc9efa458c200cf134dc7dca2c583e577a8d06ad563cfbff6` |
|
||||
| `zstd-level15.erofs` | `8ca4ba52561cc8903f85048edfd958ef692583479748dcb8aa57e2c7aade1034` |
|
||||
| `zstd-level22.erofs` | `99f79807ffd9f1216dbd624a2d8ecfcf17c9b42689fef5b5a69d0250ed84e5` |
|
||||
| `zstd-partial-ref.erofs` | `cdef7c2a397722723dcc762044612a179358f9dbe4dcd879ac08e462cf9628d1` |
|
||||
| `zstd-partial-ref-corrupt.erofs` | `ed6f4b23f6b6426e6af9180e892f03097d16a6b969edd68ad42d9a1ff8d2325e` |
|
||||
|
||||
### Source hashes
|
||||
|
||||
| Source | Size | SHA256 |
|
||||
|---|---:|---|
|
||||
| `large/large.bin` | 268435456 | `78f61f8eb37b5aeee026b579d244d766935d5655c0ac5383a96f631048611cfd` |
|
||||
| `levels/level.dat` | 8388608 | `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461` |
|
||||
| `lz4/compressed.bin` | 8388608 | `3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880` |
|
||||
| `lzma-large/large.bin` | 104857601 | `de846af6c7e47fac72c0576c449fd0fc1fe9587286c9158ae08c81804c77f66b` |
|
||||
| `microlzma/one-byte.bin` | 1 | `333e0a1e27815d0ceee55c473fe3dc93d56c63e3bee2b3b4aee8eed6d70191a3` |
|
||||
| `microlzma/block-4k.bin` | 4096 | `42b2e4ac3afeb366a6407573d5a91a961775e0fe374b0941f19681c4e3f9ea96` |
|
||||
| `microlzma/boundary-16k.bin` | 16384 | `1b5a7306ca67b75c18228d08f281eb7474b31a1a1ccbe4d680b7942fc81265b9` |
|
||||
| `partial/a.dat` | 1048576 | `c7ac0fce9c56d732e4b8328c1ca48ae33ee4d449166bd25d2c88322c1a852d9f` |
|
||||
| `partial/b.dat` | 700000 | `926a9bb05b20cb3da745eaff6e7fec38fb43d7fa155276474367550b67b97589` |
|
||||
| `partial/control.bin` | 32768 | `7544a26039c5257ee07c1280468edb1f61f0b0edaa25273b15e0252a9cc5c90a` |
|
||||
| `partial-deflate/a.dat` | 1048576 | `c7ac0fce9c56d732e4b8328c1ca48ae33ee4d449166bd25d2c88322c1a852d9f` |
|
||||
| `partial-deflate/b.dat` | 100000 | `1fbe057da0cb994652ef664a4aa00628e948578055665160fc63e7079469da21` |
|
||||
| `partial-deflate/control.bin` | 32768 | `7544a26039c5257ee07c1280468edb1f61f0b0edaa25273b15e0252a9cc5c90a` |
|
||||
| `shape/shape.dat` | 1048576 | `5be510e6b43f1ed0cda4261288ea70df11607b6ced29bc90d32ed2849ecc5e35` |
|
||||
| `ztail/inline.dat` | 131071 | `e54a4bac3b6b1c01ee846dbccdf234ddb30ed06dc13f1af1df0fbbc397431ce5` |
|
||||
| `ztail/exact-pcluster.dat` | 4096 | `14587a494e72ecdc7d26b4c0b947d91d44eba4365b252d1a4ec10ba979978c52` |
|
||||
| `ztail/one-byte-tail.dat` | 4097 | `718f4c27896edf8925300c50572df92b9127e56b2b84a3a86ced4d67c3be9596` |
|
||||
| `ztail/max-tail.dat` | 8191 | `5e1e898dba6a6bbcc8beb0b0b097bc04ecaaf4ad505b77bfb8646a3011628df6` |
|
||||
| `ztail/zero-tail.dat` | 0 | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` |
|
||||
|
||||
## Key layout fields
|
||||
|
||||
| Fixture | Structured proof |
|
||||
|---|---|
|
||||
| LZ4 compact 4K | layout 3, map offset 2183200, `h_advise=1` |
|
||||
| LZ4 full 4K | layout 1, map offset 2183200, `h_advise=0` |
|
||||
| LZ4 compact 64K | layout 3, `h_advise=7`, max physical pcluster 65536 |
|
||||
| LZ4 compact 256K | layout 3, `h_advise=7`, max physical pcluster 262144 |
|
||||
| ztailpacking | layout 3, `h_advise=9`, `h_idata_size=536`, physical bytes 1512-2048 |
|
||||
| HEAD2 | incompat `2 -> 10`, map advise `2 -> 6`, first record `1 -> 3`, pblk 1 |
|
||||
| interlaced | layout 3, `h_advise=49`, 52 compressed, 1 plain, first transition 999593 |
|
||||
| explicit attempt | layout 1, map offset 1312, `h_advise=2`, explicit bit absent |
|
||||
|
||||
The first HEAD2 logical pcluster ends at 326114; the boundary read started at
|
||||
326000. The interlaced boundary read started at 999000 and crossed the proven
|
||||
transition at 999593.
|
||||
|
||||
Partial-reference fields after reopen:
|
||||
|
||||
| Algorithm | a/b size | a/b pblk | b partial advise | b compressed blocks |
|
||||
|---|---|---|---:|---|
|
||||
| DEFLATE | 1048576 / 100000 | 1 / 1 | 32769 | 2 -> 17 |
|
||||
| ZSTD | 1048576 / 700000 | 1 / 1 | 32769 | 1 -> 1 |
|
||||
| MicroLZMA | 1048576 / 700000 | 1 / 1 | 32769 | 1 -> 1 |
|
||||
|
||||
Targeted corruption fields:
|
||||
|
||||
| Target | Algorithm | Pcluster | Patch |
|
||||
|---|---:|---|---|
|
||||
| DEFLATE `/a.dat` | 2 | 4096 + 69632 | offset 8055, length 64 |
|
||||
| ZSTD `/a.dat` | 3 | 4096 + 4096 | offset 8073, length 64 |
|
||||
| MicroLZMA `/a.dat` | 1 | 4096 + 4096 | offset 7957, length 64 |
|
||||
| HEAD2 `/shape.dat` | 0 | 4096 + 65536 | offset 4096, length 64 |
|
||||
|
||||
Every corrupted image retained a valid superblock checksum. Each read was
|
||||
bounded by `timeout 20`, returned errno 5, and left `control.bin` readable
|
||||
where the fixture included one.
|
||||
|
||||
## Build and TC145
|
||||
|
||||
The invalid build value failed with rc 1 and the exact message
|
||||
`WITH_ZSTDIO must be 0 or 1`.
|
||||
|
||||
| Build | Size | SHA256 | Undefined-symbol gate | Guest |
|
||||
|---|---:|---|---|---|
|
||||
| `WITH_ZSTDIO=0` | 73896 | `e103ebbaf7faf8041448d93022be44e7c93822a45a36a69bca2b7a2107461d09` | no `ZSTD_*`, no `bcmp` | file ID 5 load/unload PASS |
|
||||
| `WITH_ZSTDIO=1` | 78728 | `56ffcce006eed1e555121d9b8f523578272ac3d82c27a19b635d0a0a664c8370` | five formal API names, no `bcmp` | file ID 5 load/unload PASS |
|
||||
|
||||
The enabled undefined ZSTD set was exactly
|
||||
`ZSTD_DCtx_setParameter`, `ZSTD_createDCtx_advanced`,
|
||||
`ZSTD_decompressStream`, `ZSTD_freeDCtx`, and `ZSTD_isError`.
|
||||
|
||||
The disabled module read the LZ4 control hash
|
||||
`3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880`.
|
||||
A direct ZSTD mount returned rc 1 with the required message; `truss` proved
|
||||
`nmount` returned `ERR#45 EOPNOTSUPP`. The enabled module read ZSTD hash
|
||||
`ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`.
|
||||
|
||||
## Per-TC results
|
||||
|
||||
| TC | Manual evidence | Result |
|
||||
|---|---|---|
|
||||
| TC003 | compact LZ4 full hash/cmp; first 1024 bytes cmp | PASS |
|
||||
| TC004 | LZMA level 6 full hash/cmp; 102400/10240 and EOF 10000 cmp | PASS |
|
||||
| TC084 | full-index LZ4 full hash/cmp; 4000/8192 cross-extent cmp | PASS |
|
||||
| TC085 | 256 MiB full hash/cmp; 50/200 MiB ranges and two 4K offsets cmp | PASS |
|
||||
| TC086 | deterministic sequential 4K and 1 MiB block reads both full cmp | PASS |
|
||||
| TC087 | offsets 0, 4096, 65536, 1048576, and 8384512 source cmp | PASS |
|
||||
| TC088 | 4K/64K/256K images full hash and 409600/40960 source cmp | PASS |
|
||||
| TC089 | 0/4096, 4096/4096, 2048/4096, and 0/16384 source cmp | PASS |
|
||||
| TC090 | 0/65536, 4096/4096, 65504/64, and 0/262144 source cmp | PASS |
|
||||
| TC091 | ztail full hash/cmp and final 4096 bytes cmp | PASS |
|
||||
| TC092 | actual 4096, 4097, 8191, and 0-byte files full hash/cmp | PASS |
|
||||
| TC102 | DEFLATE level 1 full hash/cmp and two fixed ranges | PASS |
|
||||
| TC103 | DEFLATE level 6 full hash/cmp and two fixed ranges | PASS |
|
||||
| TC104 | DEFLATE level 9 full hash/cmp and two fixed ranges | PASS |
|
||||
| TC105 | ZSTD level 1 full hash/cmp and two fixed ranges | PASS |
|
||||
| TC106 | ZSTD level 15 full hash/cmp and two fixed ranges | PASS |
|
||||
| TC107 | ZSTD level 22 full hash/cmp and two fixed ranges | PASS |
|
||||
| TC108 | 104857601-byte LZMA level 6 full hash/cmp; 0/50/99 MiB ranges; active 0 | PASS |
|
||||
| TC109 | actual 1B/4K/16K files match; 16K inode proven compressed LZMA | PASS |
|
||||
| TC110 | valid LZMA control; targeted read EIO; same-image control; active 0 | PASS |
|
||||
| TC143 | DEFLATE/ZSTD a+b full, partial ranges, targeted EIO, controls, active 0 | PASS |
|
||||
| TC144 | MicroLZMA a+b full, partial ranges, targeted EIO, control, active 0 | PASS |
|
||||
| TC145 | invalid gate, both builds/symbol sets/KLDs, disabled rejection, enabled read | PASS |
|
||||
| TC146 | HEAD2 PASS; interlaced PASS; explicit mapped payload SHELVED | PARTIAL |
|
||||
|
||||
TC108 used a 1800-second hard timeout for each complete read under QEMU TCG.
|
||||
It completed without timeout; the long duration was diagnostic only.
|
||||
|
||||
## Explicit extent disposition
|
||||
|
||||
erofs-utils 1.8.6 source had zero matches for
|
||||
`Z_EROFS_ADVISE_EXTENTS`, `z_erofs_extent_recsize`, and the on-disk
|
||||
`struct z_erofs_extent`. The fresh `--max-extent-bytes=65536` attempt remained
|
||||
ordinary full-index metadata. The structured transformer refused an
|
||||
unverifiable metadata/payload relocation. Existing `review_fixtures.py` only
|
||||
builds a negative `pa + plen` overflow extent table, not a valid mapped
|
||||
payload.
|
||||
|
||||
FreeBSD and Linux ABI/control flow for 4/8/16/32-byte records was reviewed.
|
||||
That is static support, not dynamic positive coverage. Full details and
|
||||
acceptance criteria are in `issues/extent-metadata-fixture-unavailable.md`.
|
||||
|
||||
## Stability and cleanup
|
||||
|
||||
- Every mount was read-only and every md provider was detached.
|
||||
- All corruption probes returned errno 5 before their 20-second timeout.
|
||||
- No dmesg delta, panic, trap, or OOM was observed.
|
||||
- EROFS active allocations returned to zero after each corruption/large test.
|
||||
- Final guest verification: 25 image hashes and 19 source hashes matched.
|
||||
- Final guest: zero mounts, zero md providers, no EROFS allocator row, no EROFS
|
||||
KLD, and responsive FreeBSD 15.0-RELEASE-p8.
|
||||
- The dedicated VM was powered off and port 9225 was released after evidence
|
||||
collection.
|
||||
@@ -0,0 +1,139 @@
|
||||
# repo22 G6 Chunk and Multi-Device Manual Regression
|
||||
|
||||
Date: 2026-08-09
|
||||
Executor: G6 manual agent
|
||||
Source baseline: `6b33b4afb490be7d6fec70e499469c306a58435d`
|
||||
Scope: `TC006`, `TC093`-`TC101`, `TC118` (11 exact IDs)
|
||||
|
||||
## Result
|
||||
|
||||
PASS. All 11 requested test cases passed. There are no KFAIL, SHELVED, or
|
||||
ENV results, no duplicate IDs, and no omitted IDs.
|
||||
|
||||
| Status | Count |
|
||||
| --- | ---: |
|
||||
| PASS | 11 |
|
||||
| KFAIL | 0 |
|
||||
| SHELVED | 0 |
|
||||
| ENV | 0 |
|
||||
| Duplicate | 0 |
|
||||
| Omitted | 0 |
|
||||
|
||||
No kernel source was changed. `git diff -- src` was empty after testing.
|
||||
|
||||
## Environment and build identity
|
||||
|
||||
- Worktree: dedicated sparse worktree and branch
|
||||
`manual-g6-20260809T114414Z`, based directly on `xdm/main` at the source
|
||||
commit above.
|
||||
- VM: dedicated qcow2 overlay, QEMU TCG, 6144 MB, 4 vCPUs, SSH forward
|
||||
`127.0.0.1:9226` only.
|
||||
- Guest: FreeBSD `15.0-RELEASE-p8` amd64,
|
||||
`releng/15.0-n281036-53054229dcb3`, OSREL `1500068`.
|
||||
- Guest kernel SHA256:
|
||||
`b9abf7b58f9dd4d87f14d2fbc306cf255e6eadc47b8ae885a230f65e28be4562`.
|
||||
- Tracked build sys source: `REVISION=15.0`, `BRANCH=RELEASE-p9`, archived
|
||||
from the same repository commit; archive SHA256
|
||||
`66c457159b758ab1a5d298292b81bd29ab439ca4ab396091602e030da4721cb9`.
|
||||
- Build configuration: native guest build,
|
||||
`WITH_ZSTDIO=1 FREEBSD_SRC=/root/freebsd-src`.
|
||||
- KLD SHA256:
|
||||
`d5ff5ceef1ad76eb5b608d362b04f3bc40379e58a73d91ed3ae747c035d7b8af`.
|
||||
- repo22 source archive SHA256:
|
||||
`7562e230d165f0c0caa050684a9c1e2bd1bbb178e54a1d5273428435ae512455`.
|
||||
|
||||
The latest tracked sys source is p9 while the clean guest kernel/userland is
|
||||
p8. Both use OSREL 1500068; the module built and loaded successfully. This
|
||||
source/guest patch-level distinction is recorded rather than hidden.
|
||||
|
||||
## Fresh fixture qualification
|
||||
|
||||
`tests/g6_multidev_fixtures.py` generated every source, primary image, blob,
|
||||
flatdev, and negative image from zero with erofs-utils 1.8.6. No old test image
|
||||
or report was an input.
|
||||
|
||||
- Generator SHA256:
|
||||
`18638aee056e05099b39477fa6ad4d678ab0a74a027cfb45146ae225ff35d950`.
|
||||
- Final manifest SHA256:
|
||||
`b636a52c7b3ec7f2343f07e2d389783c8383ec07d9d9468f7552e817891c3a81`.
|
||||
- Final `SHA256SUMS` SHA256:
|
||||
`ed81d63469b40f1aba691e54a08d39737c71e66d2ff4fef963a0101fa2ad9854`.
|
||||
- Final fixture archive SHA256:
|
||||
`28be1a5cdcebb163710433c9e7c30162c1170312c34d0b286f4143480881d62f`.
|
||||
- Generated set: 62 artifacts, 17 parsed fixtures, 7 table/index negative
|
||||
images, and 10 explicit mutation assertions.
|
||||
- Reproducibility: two clean output directories had byte-identical
|
||||
`manifest.json` and `SHA256SUMS`.
|
||||
- Host fsck extraction qualified the mkfs split image, single-index image,
|
||||
explicit two/three-slot images, table-at-zero, `uniaddr=0`, packed fragment,
|
||||
and the original two-block LZ4 pcluster.
|
||||
- Guest final `sha256 -c SHA256SUMS` returned 0.
|
||||
|
||||
During qualification, an initial maximum-48-bit negative used all ones. Field
|
||||
review identified that value as the legal chunk hole sentinel. The generator
|
||||
was corrected to the largest non-NULL address `0xfffffffffffe`, regenerated
|
||||
twice, and reverified. Only the intended primary artifact changed; its final
|
||||
SHA256 is
|
||||
`d6f113cb5d2cb7da7f892632930eedf1c84fb494f5e1e4f0d2a852496ee92951`.
|
||||
The corrected kernel read returned `EINTEGRITY`.
|
||||
|
||||
## Exact results
|
||||
|
||||
| Test ID | Status | Manual evidence |
|
||||
| --- | --- | --- |
|
||||
| TC006 | PASS | Direct mkfs split primary/blob; full and cross-32K range `cmp`; external `MDIOCDETACH` returned `ERR#16 EBUSY`. |
|
||||
| TC093 | PASS | 8-byte device-ID-0 indexes with no table; full and cross-32K range `cmp` on the primary provider. |
|
||||
| TC094 | PASS | Reversed slot option order; 1/2/1 full and per-index ranges; `uniaddr=0` with nonzero ID; swapped media `ERR#6 ENXIO`; real external 8192-byte LZ4 pcluster produced the complete 131072-byte source SHA256. |
|
||||
| TC095 | PASS | First/middle/last/cross-index reads; zero-high 48-bit control and nonzero-high unified read; mapped ID 3 returned `read ERR#19 ENODEV`, unaffected file remained readable. |
|
||||
| TC096 | PASS | Parsed normal, table-at-zero, `uniaddr=0`, and high 48-bit tables; `df -k` reported 916 KiB = 229 blocks x 4096; table outside primary returned `ERR#97 EINTEGRITY`. |
|
||||
| TC097 | PASS | Six structural variants each returned `nmount ERR#97 EINTEGRITY`; after every failure md/KLD/consumer state was zero; slot-2 `ENOENT` after slot 1 open released slot 1 immediately. |
|
||||
| TC098 | PASS | Packed NID 42 whole-file fragment; size 100000; full SHA/`cmp` and 8192-byte offset range passed. |
|
||||
| TC099 | PASS | Direct primary+one-blob layout; `df -k` 900 KiB = 225 blocks x 4096; full/range compare; omitted blob option returned `ERR#6 ENXIO`. |
|
||||
| TC100 | PASS | Four providers with option order 3/1/2; full read, 262144-byte range across both transitions, and three concurrent reads passed; missing/duplicate/short returned `ERR#6/22/6`. |
|
||||
| TC101 | PASS | Explicit unified, nonzero-ID flatdev, device-ID-0 flatdev, and nonzero-high 48-bit reads matched source; gap, explicit/unified cross-slot chunk, explicit/flatdev two-block pcluster, and largest non-NULL 48-bit address all returned `read ERR#97 EINTEGRITY`. |
|
||||
| TC118 | PASS | No options/one option/short returned `ERR#6`; duplicate/primary-as-slot returned `ERR#22`; normal detach `ERR#16`; forced orphan cold slot-2 read `ERR#6`; second mount `ERR#16`; primary and external missing paths preserved `ERR#2`. |
|
||||
|
||||
## External-data proof
|
||||
|
||||
The TC094 compressed primary is 8192 bytes and contains only metadata plus its
|
||||
device table. The original pcluster blocks are absent. Its HEAD pblk is the
|
||||
slot-1 unified address, and the 8192 compressed bytes exist only in the
|
||||
external provider. FreeBSD returned the complete source SHA256
|
||||
`8dd7830946e7154d9feaaa87df6d1d39e47d51da22a690c5469dc4f2955ea0f1`;
|
||||
therefore this was an external compressed read, not table-only parsing.
|
||||
|
||||
TC101 also mounted generated combined flatdev providers with no `device.N`
|
||||
options. Both nonzero device-ID mapping and device-ID-0 unified mapping
|
||||
returned the same source SHA256 values as explicit providers. erofs-utils
|
||||
1.8.6 does not qualify these two forms, so the FreeBSD full/range comparisons
|
||||
are the runtime qualification.
|
||||
|
||||
## Errno summary
|
||||
|
||||
- `EBUSY` = 16: live md detach and second concurrent mount.
|
||||
- `ENXIO` = 6: missing/short media and forced-orphan cold I/O.
|
||||
- `ENODEV` = 19: mapped but undeclared chunk device ID 3.
|
||||
- `EINVAL` = 22: duplicate provider and primary reused as an external slot.
|
||||
- `ENOENT` = 2: nonexistent primary or external pathname.
|
||||
- `EINTEGRITY` = 97: malformed tables and complete-extent range failures.
|
||||
|
||||
## Lifecycle and platform behavior
|
||||
|
||||
Every TC ended with zero EROFS mounts, zero md units, no loaded EROFS KLD, and
|
||||
no matching GEOM consumer. TC097 repeated this after each individual malformed
|
||||
table. TC118 forced md92 orphaning, observed cold `read ERR#6 ENXIO`, then
|
||||
unmounted and released the remaining providers without panic or hang. The
|
||||
final dmesg anomaly scan for panic, fatal trap, assertion, watchdog, or EROFS
|
||||
errors was empty.
|
||||
|
||||
Linux device tables allow table offset zero and skip `uniaddr=0` during
|
||||
device-ID-0 lookup while retaining explicit nonzero-ID selection. FreeBSD
|
||||
matches those on-disk semantics but receives external providers through
|
||||
explicit `device.<slot>` mount options and owns read-only GEOM consumers.
|
||||
Consequently FreeBSD-specific `EBUSY` detach and forced-orphan `ENXIO` behavior
|
||||
was tested directly rather than inferred from Linux loop devices.
|
||||
|
||||
## Issues
|
||||
|
||||
None. No kernel failure remained after correcting the fixture's reserved hole
|
||||
sentinel, so no issue file and no source change were created.
|
||||
@@ -0,0 +1,269 @@
|
||||
# repo22 G8 Documentation, NFS, and Integrity Manual Regression
|
||||
|
||||
Date: 2026-08-09 13:07-13:56 UTC
|
||||
|
||||
Executor: G8 manual agent
|
||||
|
||||
Source baseline: `f11fff5b8e8050e1017ed86f0bcf71042b2b45aa`
|
||||
|
||||
Exact scope: `TC111`, `TC131`-`TC133`, `TC154`-`TC156` (7 IDs)
|
||||
|
||||
## Result
|
||||
|
||||
All seven requested test cases passed. There are no KFAIL, SHELVED, or ENV
|
||||
results, no duplicate IDs, and no omitted IDs.
|
||||
|
||||
| Status | Count |
|
||||
| --- | ---: |
|
||||
| PASS | 7 |
|
||||
| KFAIL | 0 |
|
||||
| SHELVED | 0 |
|
||||
| ENV | 0 |
|
||||
| Duplicate | 0 |
|
||||
| Omitted | 0 |
|
||||
|
||||
| Test ID | Status | Manual evidence |
|
||||
| --- | --- | --- |
|
||||
| TC111 | PASS | Audited current source and documentation against real generic `/sbin/mount`; corrected helper and writable-request claims. |
|
||||
| TC131 | PASS | Real mountd/nfsd NFSv3 export, four vnode types, read-only behavior, 16-byte handle resolution, and RPC statistics. |
|
||||
| TC132 | PASS | Handle ABI/classes, stable remount, replacement `ESTALE`, metabox and external-provider regressions, and nfsd restart. |
|
||||
| TC133 | PASS | Exact 12,050-name READDIRPLUS lists, five cold remounts, 20 individually waited workers, and zero RPC errors. |
|
||||
| TC154 | PASS | Same-superblock/UUID/NID replacement changed per-inode generation; unchanged remount stayed stable. |
|
||||
| TC155 | PASS | Explicit-extent physical-address wrap returned `EINTEGRITY` on both direct reads and in `truss`. |
|
||||
| TC156 | PASS | Compact/extended epoch, `time_t`, and nanosecond boundary corruptions returned `EINTEGRITY`. |
|
||||
|
||||
No kernel source changed. No kernel failure remained, so no issue file was
|
||||
created.
|
||||
|
||||
## Isolated Environment
|
||||
|
||||
- Worktree: `/work/build/repo22-manual-g8-20260809T130723Z`, branch
|
||||
`manual-g8-20260809T130723Z`, created directly from the source baseline.
|
||||
- VM: independent qcow2 overlay backed by the clean FreeBSD development base;
|
||||
QEMU TCG, 6144 MB, 4 vCPUs, SSH only on `127.0.0.1:9228`.
|
||||
- Guest: FreeBSD `15.0-RELEASE-p8` amd64,
|
||||
`releng/15.0-n281036-53054229dcb3`, OSREL `1500068`.
|
||||
- Guest kernel SHA256:
|
||||
`b9abf7b58f9dd4d87f14d2fbc306cf255e6eadc47b8ae885a230f65e28be4562`.
|
||||
- Host erofs-utils: 1.8.6.
|
||||
- No CI, runner, or test wrapper was used. Markdown procedures were issued
|
||||
manually through SSH, with direct command status kept separate from tracing
|
||||
tool status.
|
||||
|
||||
## Exact Build
|
||||
|
||||
The repo22 source archive came from the exact baseline commit. The matching
|
||||
tracked FreeBSD build input was `dev-freebsd-releng/sys`, identified by
|
||||
`REVISION="15.0"` and `BRANCH="RELEASE-p9"`. The build ran natively in the
|
||||
guest as:
|
||||
|
||||
```sh
|
||||
FREEBSD_SRC=/root/freebsd-src WITH_ZSTDIO=1 ./build.sh
|
||||
```
|
||||
|
||||
The compile command contained `-DZSTDIO` and kernel `-Werror`. The module had
|
||||
the five expected `ZSTD_*` kernel references and no unresolved `bcmp`.
|
||||
|
||||
| Object | SHA256 |
|
||||
| --- | --- |
|
||||
| repo22 source archive | `2f76ef7df9117c6e7d62ad41809638ad306e676191346083bf95fe551f7df515` |
|
||||
| FreeBSD 15 `sys` archive | `cd806ac4d6aee5d7ceb6a2f9603020b48b5012835bc8c7f2ec0985572fcd0262` |
|
||||
| `erofs.ko` | `e8cd4839328de089355505efb1629c835d2f93faad2e14f63c6e6cb844ce9589` |
|
||||
| `nfs_fh_tool` | `db704f67d718d04b84ed32b5cdbecb93a99c5fe3b4ab238d603a10e005d790e3` |
|
||||
|
||||
The guest kernel is p8 and the tracked source is p9, but both use the FreeBSD
|
||||
15.0 OSREL ABI. The exact module loaded and unloaded successfully.
|
||||
|
||||
## Fixture Qualification
|
||||
|
||||
`tests/review_fixtures.py` generated two independent output directories. Their
|
||||
manifests and checksum inventories were byte-identical, and all nine image
|
||||
checksums self-verified. Its structured assertions recorded:
|
||||
|
||||
- target NID 452 at inode offset 14464 after checksum block 4096;
|
||||
- identical complete superblock block and UUID for NFS images A and B;
|
||||
- generations `3895653226 -> 548470773` from the changed raw inode;
|
||||
- explicit extent base `0xfffffffffffff000`, `plen0=8192`, `plen1=4096`, and
|
||||
wrap before logical cluster 4096;
|
||||
- compact epoch wrap/range, compact nanoseconds 1000000000, extended
|
||||
nanoseconds 1000000000, and extended seconds `INT64_MAX+1`.
|
||||
|
||||
Review generator SHA256 was
|
||||
`b92fd6b6122d55882998138efc0109e24b236f9e877405dbff7914284a18c124`;
|
||||
manifest SHA256 was
|
||||
`be0836043d172b34eb625e5259f7e6cb9a8992ff74e7d1a5cdc10d69282e2335`.
|
||||
|
||||
The NFS stress image was generated from a fresh deterministic tree containing
|
||||
12,050 `bigdir` files, 256 concurrent-read files, four basic vnode types, and a
|
||||
22,020,096-byte throughput file. Image A SHA256 was
|
||||
`88721877dc3762c8eafdf3bcca2653787cdcf41983978ab159705761abefac37`;
|
||||
image B was
|
||||
`964b3d75579740c9b08bc9cae8329e3d641d3c505e9391de3ab9f5d24fbecd17`.
|
||||
The expected sorted-name list SHA256 was
|
||||
`4fbf1b6103e8884223629c3fd03f4ec36061e65cd2a1becfc96670daa96f9d71`.
|
||||
|
||||
Fresh structured metabox and multidevice helpers also self-verified the two
|
||||
TC132 regression inputs. The metabox image SHA256 was
|
||||
`dd7b04097d1bfe283b65c95d759acd2176beb7cb0f1fe9d5ddbc0694b5acba9d`;
|
||||
the multidevice primary/slot hashes were `918aa3a64e8861011914d967912909ce9de98418cff711cfbf8f91c0c7717f72`,
|
||||
`4e31d95a12405f2f1396fb926a6854c591fc335eade8944f73fdc7fe6758aee6`,
|
||||
and `355a74880bd3648dea22ad18e19f809cee12303ceed2a99f53094c2b66563a11`.
|
||||
|
||||
## TC111
|
||||
|
||||
`/sbin/mount_erofs` was absent and `command -v mount_erofs` returned 127.
|
||||
Generic `/sbin/mount -t erofs` mounted the image successfully without `-o ro`.
|
||||
The following real behavior was recorded:
|
||||
|
||||
- the default mount was read-only and `touch` failed with `EROFS`;
|
||||
- `-o rw` returned 0 but the resulting mount was still read-only;
|
||||
- `-o ro,noexec,nosuid` returned 0 and all three flags appeared;
|
||||
- non-export `mount -u` returned `EOPNOTSUPP`;
|
||||
- an unknown filesystem option returned direct rc 1, with
|
||||
`nmount(..., MNT_RDONLY) ERR#22`, and created no mount.
|
||||
|
||||
Current `src`, `README.md`, `docs/features.md`, `docs/architecture.md`,
|
||||
`docs/erofs.5`, and current progress material were checked. `README.md`,
|
||||
`docs/erofs.5`, and the test procedure now describe the generic frontend and
|
||||
the forced-read-only `rw` behavior accurately. The man page's split-device
|
||||
example was also exercised by the TC132 multidevice mount. SEE ALSO entries
|
||||
absent from the qualified guest were removed; `mandoc -Tlint -Werror` and
|
||||
ASCII rendering both returned 0 in that guest.
|
||||
|
||||
## TC131
|
||||
|
||||
The direct EROFS line lacked `NFS exported` before mountd. After installing the
|
||||
loopback export and reloading mountd, `showmount -e` listed the exact path and
|
||||
the direct mount gained `NFS exported`. `rpcinfo` showed NFSv3 and mountd over
|
||||
TCP and UDP.
|
||||
|
||||
The NFSv3 TCP client negotiated `rdirplus`. Regular file content, directory,
|
||||
symlink target, FIFO type, and inode number matched the direct EROFS mount.
|
||||
`touch` failed with `Read-only file system`. Local `fhstat` and `fhopen`
|
||||
resolved the regular handle with `len=16`, `pad=0`, NID 116, and generation
|
||||
2068234290.
|
||||
|
||||
After basic operations client READDIRPLUS was 2. Server Write and Create were
|
||||
both 0. TimedOut, Invalid, X Replies, and Retries were all 0.
|
||||
|
||||
## TC132
|
||||
|
||||
Regular, directory, symlink, and FIFO handles all had `len=16`, `pad=0`, full
|
||||
64-bit NIDs, and nonzero generations matching `st_gen`. Bad length/pad returned
|
||||
`EINVAL`; bad generation/NID returned `ESTALE` through both `fhstat` and
|
||||
`fhopen`.
|
||||
|
||||
The unchanged image remounted on md80 preserved the complete handle SHA256
|
||||
`9e1df1952b43d383fea295301525ede7a69b7d57e5f60a75a6d7f1531e13df0a`,
|
||||
and the old handle still read the file. Replacement B retained fsid and NID
|
||||
116 but changed generation `2068234290 -> 857800581`; both old-handle paths
|
||||
returned `ESTALE` while B resolved.
|
||||
|
||||
The metabox handle preserved NID `0x8000000000000010`; invalid metabox NID and
|
||||
generation returned `ESTALE`. After forced orphaning of external slot 2, the
|
||||
valid file handle still resolved metadata and the read returned `ENXIO(6)`,
|
||||
not `ESTALE`.
|
||||
|
||||
An NFS client descriptor remained open across `service nfsd onerestart 3<&-`.
|
||||
The old descriptor and a new path read both returned SHA256
|
||||
`3e2d0b4971e1f4bf00c3562a8bf9c5d85378c9de054f3da89541f49f2b449a7b`;
|
||||
the export and all RPC registrations remained live.
|
||||
|
||||
## TC133
|
||||
|
||||
Requested readdir sizes 512, 1024, and 4096 were all clamped by the FreeBSD
|
||||
client to 8192; the default remained 65536. This is the recorded client floor.
|
||||
|
||||
Every one of the four mounts returned exactly 12,050 unique expected names.
|
||||
Every sorted list, plus all five cold-remount lists, had SHA256
|
||||
`4fbf1b6103e8884223629c3fd03f4ec36061e65cd2a1becfc96670daa96f9d71`.
|
||||
The `ls -a1` list contained 12,052 entries, with `.` and `..` exactly once.
|
||||
|
||||
All 12 traversal workers were waited by PID and returned 0. All four parallel
|
||||
cat workers and four parallel stat workers were also waited by PID and returned
|
||||
0. The informational 22,020,096-byte read completed in 14.51 seconds.
|
||||
|
||||
Final client/server READDIRPLUS was 3420. TimedOut, Invalid, X Replies, and
|
||||
Retries remained 0. Server Write, WriteRPC, Create, and Commit remained 0.
|
||||
There were no new stale-handle, timeout, retry, panic, trap, assertion,
|
||||
watchdog, or EROFS error lines.
|
||||
|
||||
## TC154
|
||||
|
||||
Image A produced fsid `00000034:000000e0`, `len=16`, `pad=0`, NID 452, and
|
||||
generation 3895653226. `nfs_fh_tool`, `stat st_gen`, and the fixture manifest
|
||||
agreed. The unchanged remount preserved complete handle SHA256
|
||||
`79f2f4b8e354af0ae369849d7f66a739162ffadc4a5689c09643c9949b548de1`
|
||||
and the old handle remained readable.
|
||||
|
||||
Image B retained the complete superblock block, UUID, fsid, NID, and content,
|
||||
but generation became 548470773. Both old-handle paths returned `ESTALE`; B's
|
||||
handle resolved. The malformed same-NID replacement returned positive errno 45
|
||||
through both paths.
|
||||
|
||||
## TC155
|
||||
|
||||
The self-checked fixture SHA256 was
|
||||
`1e49ecf1917265fde9b606e80f7dbae8b14aca8c6f149128b31d6c825da546dd`.
|
||||
The image mounted and target `stat` reported inode 40, size 1,048,576. Two
|
||||
direct one-byte reads at logical offset 4096 returned rc 1 and `Integrity check
|
||||
failed`; the traced read was `ERR#97`. The repeat exercised the cached vnode.
|
||||
A DTrace `io:::start` positive control observed one raw
|
||||
`md90 offset=4096 bytes=4096` event. A separate trace around the failing EROFS
|
||||
read observed zero md90 events, proving the wrapped address did not reach the
|
||||
provider. No panic or resource remained.
|
||||
|
||||
## TC156
|
||||
|
||||
Both compact root corruptions failed `nmount` with `ERR#97`. The compact range
|
||||
image mounted with root mtime `INT64_MAX`; target stat returned `ERR#97` for
|
||||
`INT64_MAX+1`. Both extended images mounted, and target stat returned `ERR#97`
|
||||
for nanoseconds 1000000000 and seconds `INT64_MAX+1`. Every failure was run
|
||||
directly and again under `truss`; no panic or resource remained.
|
||||
|
||||
## Cleanup and Restoration
|
||||
|
||||
Before NFS setup, `/etc/exports` did not exist; rpcbind, mountd, and nfsd were
|
||||
stopped; and `rpcbind_enable`, `mountd_enable`, and `nfs_server_enable` were all
|
||||
`NO`. Cleanup unmounted every loopback client while nfsd was alive, cleared and
|
||||
reloaded exports, stopped nfsd/mountd/rpcbind, then removed the direct EROFS
|
||||
mount, md42, and the module.
|
||||
|
||||
Final state exactly matched the baseline: `/etc/exports` absent, all three
|
||||
services stopped with rc 1, all three rc settings `NO`, zero NFS/EROFS mounts,
|
||||
zero md providers, no EROFS KLD, no service process, and `rpcinfo` refused the
|
||||
connection because rpcbind was stopped. The complete NFS-period dmesg delta had
|
||||
zero anomaly lines.
|
||||
|
||||
The retained guest evidence archive is outside the repository at
|
||||
`/work/build/repo22-manual-g8-20260809T130723Z-vm/evidence/`:
|
||||
|
||||
| Artifact | SHA256 |
|
||||
| --- | --- |
|
||||
| `final-guest-evidence.txt` | `eb67210818e663c15e4670ada9ba63b82b44e9ed3a9486c575df3a71ed3f0522` |
|
||||
| `TC155-dtrace-assert.txt` | `f8b52985a74c8c777b3b830c39749be65c613165ffbcde6d0018dc471018c433` |
|
||||
| `final-post-dtrace-cleanup.txt` | `90f04d8f5a4ae35c87cc3adb1aeb939c7cc5534233ed63e9ff4501c83fd857d2` |
|
||||
| `repo22-g8-evidence.tar.gz` | `9a378fd619f9bca21f40689d746308933aa22e440f7201104732f91634c21290` |
|
||||
|
||||
## Non-Qualified Attempts
|
||||
|
||||
One TC131 setup command stopped at the expected nonzero service-status probe
|
||||
because that probe was mistakenly under `set -e`; it occurred before module,
|
||||
exports, or service changes. One TC133 attempt stopped after the four exact
|
||||
lists because a shell-quoted dot assertion matched zero; the three added
|
||||
clients were unmounted and the complete procedure was rerun from the start.
|
||||
Tracing wrappers returned 0 even when their child failed, so all error claims
|
||||
use separate direct statuses plus syscall errno. None of these discarded
|
||||
attempts is counted as PASS evidence.
|
||||
|
||||
Two initial DTrace specifications used field names from other provider ABIs
|
||||
and failed at compile time before any probe I/O. The qualified specification
|
||||
used FreeBSD `struct devstat` and `struct bio` fields. Its first assertion used
|
||||
a line anchor even though the format emitted a literal `\\n`; substring counts
|
||||
then proved one positive md90 event and zero failing-read md90 events. DTrace's
|
||||
automatically loaded module set was unloaded as a unit and the original KLD
|
||||
set was reverified.
|
||||
|
||||
## Issues
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,183 @@
|
||||
# repo22 G7 boundary and stress full manual regression report
|
||||
|
||||
## Scope and result
|
||||
|
||||
- Exact scope: TC120-TC130 inclusive.
|
||||
- Count check: 11 requested, 11 executed, 0 duplicate, 0 omitted.
|
||||
- Result: **PASS 11, KFAIL 0, SHELVED 0, ENV 0**.
|
||||
- No repo22 kernel source was changed.
|
||||
- No kernel failure occurred, so no G7 issue file was opened.
|
||||
- No CI, runner, or test wrapper was used. Each test's Markdown commands were
|
||||
issued manually in the dedicated FreeBSD guest.
|
||||
|
||||
## Baseline and source identity
|
||||
|
||||
- Requested initial `xdm/main`: `c566d8ac6bf8e801082bbccf108451f6ee46ad40`.
|
||||
- G6 pushed first; G7 fetched and rebased onto
|
||||
`f11fff5b8e8050e1017ed86f0bcf71042b2b45aa` before editing.
|
||||
- Exact build commit:
|
||||
`e44e24c2d7e4955498fb42b06bcf376d6c452190`.
|
||||
- The equivalent G7 test/source commit after the final remote rebase is
|
||||
`896683f14af2ba36705498f85d0d8af02b513d31`; its repo22 source tree is
|
||||
byte-identical to the build input.
|
||||
- Exact `repo-community/repo22/src` tree:
|
||||
`34a56a583b3b43e7e103b26a30dea1d400c2bc6c`.
|
||||
- G8 later advanced `xdm/main` to `6f336d0a7`; its repo22 source tree was the
|
||||
same `34a56a58...`. G7 rebased before final push without force.
|
||||
- Build configuration: `WITH_ZSTDIO=1`.
|
||||
- Tracked FreeBSD sys source: `REVISION="15.0"`, `BRANCH="RELEASE-p9"`.
|
||||
- repo22 source archive SHA256:
|
||||
`e715e9d323be1448e0395add99cf017dea3c1bbb6579915cf3d07f7265ca2891`.
|
||||
- FreeBSD sys archive SHA256:
|
||||
`71f535ab5a9ef686c6adbe123a1d1e65fd9bfa9cd19724f9fcef222a8efbe9d8`.
|
||||
|
||||
## Isolated environment
|
||||
|
||||
- Worktree: `/work/build/repo22-manual-g7-20260809T125507Z`.
|
||||
- Branch: `manual-g7-20260809T125507Z`.
|
||||
- Artifact root: `/work/build/repo22-g7-20260809T125507Z`.
|
||||
- Dedicated qcow2 overlay backed by `/work/build/vm-freebsd-dev-base.qcow2`.
|
||||
- Dedicated SSH forward: `127.0.0.1:9227`.
|
||||
- QEMU: TCG multi-thread, `qemu64`, 6144 MiB RAM, 4 vCPUs, virtio disk/network.
|
||||
- Guest: FreeBSD `15.0-RELEASE-p8`, amd64,
|
||||
`releng/15.0-n281036-53054229dcb3`, OSREL `1500068`.
|
||||
- Guest kernel SHA256:
|
||||
`b9abf7b58f9dd4d87f14d2fbc306cf255e6eadc47b8ae885a230f65e28be4562`.
|
||||
- Host erofs-utils: 1.8.6.
|
||||
- RACCT/RCTL was initially present but disabled. The dedicated overlay set
|
||||
loader tunable `kern.racct.enable="1"`, rebooted, then reported enabled with
|
||||
an empty initial rule set.
|
||||
|
||||
## Exact KLD and probe
|
||||
|
||||
The KLD built successfully from the archived commit with the tracked p9 sys
|
||||
tree and loaded by full pathname. `kldstat -v` assigned file ID 5 and showed
|
||||
module ID 507 for `erofs`.
|
||||
|
||||
| Artifact | SHA256 |
|
||||
|---|---|
|
||||
| `/root/repo22-g7/erofs.ko` | `06dfa530efb2d494950672d60453d86a6e70f06ca2ce7b4eff3396ca8018c76d` |
|
||||
| `/root/repo22-g7/g7_probe` | `a0bca6ea596cec6063bc058aebee12929a1814e6d343283ffbe051551bf75c57` |
|
||||
|
||||
The KLD was an amd64 FreeBSD relocatable object with build ID
|
||||
`d4fff529ae8e4c5395e1e867fd4ea5d57a9570b6`. The expected formal FreeBSD ZSTD
|
||||
undefined symbols were present; no build or load error occurred.
|
||||
|
||||
## Reproducible fixture
|
||||
|
||||
Two fresh generations produced byte-identical source inventories, source
|
||||
checksum lists, and image checksum lists. Each generation independently ran
|
||||
the helper verification and erofs fsck checks.
|
||||
|
||||
```text
|
||||
source file count = 24023
|
||||
image count = 2
|
||||
SOURCE-INVENTORY.tsv SHA256 = b9be1745900a8cf6755068b6a78b93bc294cf8857539159b7771e5170bc1b9ed
|
||||
SOURCE-SHA256SUMS SHA256 = 445929465a3031e84de9887fc5b02d72cb797df7ca512ea1cc874d11f77747d3
|
||||
SHA256SUMS SHA256 = f9cf45c1e3fe814290d9cd0b96d066509309cc3f764c545d5e7d7ddae4a9eac3
|
||||
fixture-manifest.json SHA256 = 0ba86269962b00329b99177494cfb099c0f58fe5de77d2dc1f12da684dedf09d
|
||||
guest image/source verification = 2 images and 24023 sources, all OK
|
||||
```
|
||||
|
||||
| Image | Bytes | SHA256 |
|
||||
|---|---:|---|
|
||||
| `boundaries.erofs` | 37752832 | `ff7e804dc309039fa5dfa9b6e75416d16224137c8f6d6d96bf04cfa0ba8a1a8c` |
|
||||
| `workloads.erofs` | 155111424 | `5f21a045114c5956b80752736c9833fe6a975a296cd28324734c9b9e423f3984` |
|
||||
|
||||
The sparse source was exactly 4294971393 bytes. It occupied 48 Linux
|
||||
512-byte blocks before transfer and 512 FreeBSD blocks with a reported
|
||||
32768-byte file-system block size after sparse-aware extraction. All five
|
||||
markers and six boundary ranges passed the helper's source self-check.
|
||||
|
||||
## Per-TC results
|
||||
|
||||
| TC | Manual evidence | Result |
|
||||
|---|---|---|
|
||||
| TC120 | Exact size 0; source/mount empty SHA256 `e3b0c442...`; read returned 0; SET/END seeks returned 0 | PASS |
|
||||
| TC121 | Exact 4294971393-byte size; six source-compared ranges across block, hole, 2 GiB, 4 GiB, and EOF boundaries; EOF read 0 | PASS |
|
||||
| TC122 | Exactly 128 levels; deepest source/mount hash `ef151b18...`; full cmp and exact `pwd -P` | PASS |
|
||||
| TC123 | Exact 255-byte name; one exact readdir row; size 4096; source/mount hash `0724316b...` | PASS |
|
||||
| TC124 | Exactly 12000 one-KiB files and exact names; complete source/target hash-list digest `be93ef11...` | PASS |
|
||||
| TC125 | Exactly 12000 direct entries and exact names/hashes; five boundary lookups matched; readdir timing recorded | PASS |
|
||||
| TC126 | Exactly 16 saved PIDs, 16 waits, all rc 0; every expected/actual full-file hash matched; no PID remained | PASS |
|
||||
| TC127 | Three RCTL-capped allocators reached ENOMEM, released all touched bytes, recovered allocation, and wait=0; pressure read hash/cmp matched | PASS |
|
||||
| TC128 | 268435456-byte source/mount hash and full cmp matched; three complete sequential reads rc 0; metrics recording-only | PASS |
|
||||
| TC129 | Three deterministic 16384-operation runs, all source-compared, zero mismatch, identical digest; metrics recording-only | PASS |
|
||||
| TC130 | Seven saved PIDs and seven wait=0 results; sequential, hash, two random, 16-file, names, and stat workloads all matched; full cleanup | PASS |
|
||||
|
||||
TC121 range digests were `7775e9e82783e06f`, `c86358c1fb3fabf7`,
|
||||
`b43a063055adc383`, `a1bd19f7b12c60b2`, `5a378d4efc494af4`, and
|
||||
`1873eaea77354aa6`, in manifest order.
|
||||
|
||||
TC124's complete 12000-row source and mounted hash-list files both had SHA256
|
||||
`be93ef11d2e4cd0cd42ef057397b89978b624e42674f9181e5dd151b30636171`.
|
||||
TC125's complete 12000-row files both had SHA256
|
||||
`8cde4ea887f9b21b265ac199bcbb447a656aa6d718a36424f4fa03a6c538ebe7`.
|
||||
|
||||
TC126 persisted each worker's PID, source hash, mounted hash, and wait status.
|
||||
The PID table SHA256 was `fc67656d...`; the 16-row wait table SHA256 was
|
||||
`888f026d...`. Every wait code was 0 and every hash pair was equal.
|
||||
|
||||
## Pressure evidence
|
||||
|
||||
TC127 used three synchronized `g7_probe` processes and valid FreeBSD RCTL
|
||||
rules `process:<pid>:vmemoryuse:deny=640M`. The first process utilization
|
||||
sample showed `memoryuse=532M` and `vmemoryuse=636M`.
|
||||
|
||||
| Worker | Allocated before ENOMEM | errno | Released | Recovery | wait rc |
|
||||
|---|---:|---:|---:|---|---:|
|
||||
| 1 | 553648128 | 12 | 553648128 | ok | 0 |
|
||||
| 2 | 545259520 | 12 | 545259520 | ok | 0 |
|
||||
| 3 | 553648128 | 12 | 553648128 | ok | 0 |
|
||||
|
||||
Free memory recorded by `vmstat -H` was about 4.01 GB before, 2.35 GB while
|
||||
the allocations were held, and 3.89 GB after release. The 96 MiB source and
|
||||
mounted file both hashed to
|
||||
`e4baaad480721bfb69a0315f0092fcf1ad75426fda0b9242a750b5685ad75152`
|
||||
and full `cmp` exited 0. RCTL rules and synchronization files were removed.
|
||||
|
||||
## Recording-only metrics
|
||||
|
||||
TC125's full 12000-entry sorted readdir scan under TCG recorded `real 11.48`,
|
||||
`user 0.81`, and `sys 9.76` seconds. It would have failed the old invalid
|
||||
10-second fixed threshold despite complete correctness, so timing was
|
||||
correctly treated as recording-only.
|
||||
|
||||
TC128 sequential reads:
|
||||
|
||||
| Run | Bytes | Seconds | Bytes/s | wait rc |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 1 | 268435456 | 6.764841 | 39680970 | 0 |
|
||||
| 2 | 268435456 | 9.207558 | 29153816 | 0 |
|
||||
| 3 | 268435456 | 10.603392 | 25315998 | 0 |
|
||||
|
||||
TC129 used seed `0x6a09e667f3bcc909`, 16384 operations, and 4096-byte
|
||||
blocks. All runs produced digest `908bc2905f48695d` and zero mismatches.
|
||||
|
||||
| Run | Seconds | IOPS | Mean us |
|
||||
|---|---:|---:|---:|
|
||||
| 1 | 2.236047 | 7327.22 | 136.48 |
|
||||
| 2 | 1.062087 | 15426.24 | 64.82 |
|
||||
| 3 | 1.026698 | 15957.95 | 62.66 |
|
||||
|
||||
TC130 random workers ran concurrently with the other five workloads. Seed
|
||||
`0xbb67ae8584caa73b` recorded 442.73 IOPS, 2258.72 us mean, digest
|
||||
`78e9d2c7f86ed20e`; seed `0x3c6ef372fe94f82b` recorded 455.91 IOPS,
|
||||
2193.42 us mean, digest `ee76a5d094a0c6f2`. Both had zero mismatches.
|
||||
The exact names files shared SHA256 `8d1cf816...`; exact stat files shared
|
||||
SHA256 `ca8937b1...`.
|
||||
|
||||
## Stability and cleanup
|
||||
|
||||
- Final dmesg delta was empty; its SHA256 was the empty-file hash. No panic,
|
||||
trap, assertion, watchdog, OOM, or EROFS error appeared.
|
||||
- Every test detached its exact md provider after unmounting.
|
||||
- Final guest state had zero EROFS mounts, md providers, G7 child processes,
|
||||
RCTL rules, synchronization files, and Python bytecode caches.
|
||||
- KLD file ID 5 was unloaded with `kldunload -i 5`; no EROFS allocator row or
|
||||
KLD remained.
|
||||
- The guest remained responsive, powered off normally, QEMU PID 344535 exited,
|
||||
port 9227 was released, and the dedicated overlay/log/PID files were deleted.
|
||||
- Compressed evidence archive SHA256:
|
||||
`f18246076e5ff93309187a1826afe9f2dfa4a0bcdf7a958419d2890d05727ccd`.
|
||||
- No issues were created because all correctness gates passed.
|
||||
@@ -0,0 +1,119 @@
|
||||
# repo22 Final Review Independent Manual Regression
|
||||
|
||||
Execution window: 2026-08-09 18:04-18:29 UTC
|
||||
|
||||
Source baseline: `fcc85b93d5f8fd9671686bd68bf3b086c8bd25cf`
|
||||
|
||||
Scope: TC157-TC161, final dual-configuration build and mount smoke, TC111
|
||||
coverage-matrix follow-up, and resource hygiene. No CI runner or automated
|
||||
kernel-test harness was used.
|
||||
|
||||
## Result
|
||||
|
||||
All five assigned test cases passed.
|
||||
|
||||
| Test ID | Status | Manual evidence |
|
||||
| --- | --- | --- |
|
||||
| TC157 | PASS | Six 16/32-byte descending, duplicate, and cross-branch tables returned positive `EINTEGRITY`; repeated lookup agreed; a raw offset-4096 control was observable while failed lookups produced no payload read. |
|
||||
| TC158 | PASS | A 17,592,186,056,704-byte sparse provider produced direct and file-handle `st_blocks=34359738384`; two FBT returns recorded `va_bytes=17592186052608`; the file remained readable. |
|
||||
| TC159 | PASS | FIFO size-only setattr returned 0; size plus mode, owner, times, and all fields returned `EROFS(30)`; metadata remained `10640:0:0:0:0`. |
|
||||
| TC160 | PASS | Both `OFF_MAX` and zero-offset `getdirentries` calls returned `EINTEGRITY(97)` and preserved their offsets; independent truss SHA256 was `6df9c594bfba8aaf849fa7105fda09a5cab99d79f04c42901a438321c90700e0`. |
|
||||
| TC161 | PASS | Both normal configurations built, the private `nm` shim failed before publication without a SUCCESS line or temporary file, the previous KLD hash stayed unchanged, and a final normal build used `/usr/bin/nm` and exited 0. |
|
||||
|
||||
The TC157 fixtures do not cover a first nonzero `lstart` or an extreme
|
||||
extent-count performance boundary. These are residual coverage limits, not
|
||||
observed failures. Positive mapped-payload coverage remains the separate TC146
|
||||
PARTIAL issue.
|
||||
|
||||
## Exact Inputs
|
||||
|
||||
The source archive exported from the baseline had SHA256
|
||||
`a0c330601f9d7b2aa246a17e67bcd67914d457480cb69df025d09bc34d8b7b31`.
|
||||
The independent fixture archive had SHA256
|
||||
`bf8ab5430f06dfc067f041002cc7861b0b110dc00d9c447a82877f21b67b09ed`.
|
||||
Its manifest and evidence hashes were
|
||||
`b880be5d214985e6a7f75cc77fea333f046a30963a9d310d57a6cdc67104e740`
|
||||
and `ace71446671b3d17e6a46997f43fc605a7a6bb5993c9de0e6e12cd223746b317`.
|
||||
|
||||
The fixture evidence recorded:
|
||||
|
||||
- complete TC157 logical-start arrays and old binary-search visit indices;
|
||||
- TC158 `blocks_lo=2`, `blocks_hi=1`, and 4,294,967,298 data blocks;
|
||||
- TC159 compact FIFO mode `010640`;
|
||||
- TC160 `i_size=9223372036854775807` and expected errno 97.
|
||||
|
||||
## TC161 Build Qualification
|
||||
|
||||
The shim log SHA256 was
|
||||
`a1a83797ba2c0111b66bb8783f3a67087420ef7a9bccc93fd42fb94c9c24c593`.
|
||||
It contained `ERROR: nm failed while checking erofs.ko`, no SUCCESS line, and
|
||||
left no `nm-undef.*` file. The post-shim status file recorded `exit=0` and
|
||||
`nm=/usr/bin/nm`; its build log SHA256 was
|
||||
`5ce2519a85de7a4846f95fdb858fd7818182aad3fa55a7ea8d06cf7687143f11`.
|
||||
|
||||
The post-shim build used an explicit normal path and environment:
|
||||
|
||||
```sh
|
||||
env PATH=/usr/bin:/bin:/usr/sbin:/sbin \
|
||||
FREEBSD_SRC=/tmp/repo22-freebsd15-src \
|
||||
WITH_ZSTDIO=0 ./build.sh
|
||||
```
|
||||
|
||||
Its key log lines were `==> Building repo22 erofs.ko` and
|
||||
`==> SUCCESS: .../build/erofs.ko`.
|
||||
|
||||
Final exact-source builds used FreeBSD 15 kernel `-Werror`:
|
||||
|
||||
| Configuration | KLD SHA256 | Result |
|
||||
| --- | --- | --- |
|
||||
| `WITH_ZSTDIO=0` | `15fda9d334132cd81769ce4dff4f8411a6e2b4cf7531c352530d0de85f42a2d2` | PASS |
|
||||
| `WITH_ZSTDIO=1` | `23782dc0ce7da188807d35020bf2d8c6044b796c5c9a8746b398ba784cd6ad4e` | PASS |
|
||||
|
||||
The final ZSTD build log SHA256 was
|
||||
`e58bc73d1b0b8295d8baba0cf0ce07bf4e9c994837e65c7ccdd731dc91edb283`.
|
||||
|
||||
## Final Module Smoke and Cleanup
|
||||
|
||||
Each final KLD loaded independently, mounted `special-setattr.erofs` through a
|
||||
fresh vnode md provider, exposed the expected FIFO, unmounted, detached the md
|
||||
unit, and unloaded. The smoke log SHA256 was
|
||||
`7ced7329da9f39527cf903213dee4c99dbe6d8d6fa96e55bca3427c4f4a56420`.
|
||||
|
||||
Final guest state was:
|
||||
|
||||
```text
|
||||
residual-mount-count=0
|
||||
residual-md=
|
||||
residual-kld-count=0
|
||||
smoke-exit=0
|
||||
```
|
||||
|
||||
The host repo22 ignored `build/`, old ignored generated fixture/artifact trees,
|
||||
and `__pycache__` content were removed before aggregation. No generated KLD,
|
||||
object, image, or Python bytecode is acceptance evidence in Git.
|
||||
|
||||
## TC111 Coverage-Matrix Follow-up
|
||||
|
||||
A bounded filename audit found 162 specifications, TC000 through TC161, with
|
||||
162 unique IDs, no duplicate, and no missing ID. TC000 is a template and is not
|
||||
counted as executable.
|
||||
|
||||
The eight canonical G1-G8 report tables contained exactly 156 rows and 156
|
||||
unique IDs covering TC001-TC156, with no duplicate or omission. TC157-TC161
|
||||
add five unique executable cases. TC060's original G3 failure is superseded by
|
||||
its dated fixed-source PASS report; TC153's G3 result is PASS.
|
||||
|
||||
The canonical final result is therefore:
|
||||
|
||||
```text
|
||||
Executable test cases: 161
|
||||
PASS: 160
|
||||
PARTIAL: 1 (TC146)
|
||||
FAIL/KERNEL-FAIL/ENV/SHELVED_TC: 0
|
||||
```
|
||||
|
||||
TC146's shelved positive mapped-payload subitem is not counted as an additional
|
||||
test case. G1-G8 evidence was collected across multiple source commits; this
|
||||
report does not claim that every historical test ran on the final KLD. The
|
||||
final `fcc85b93d` code baseline received the independent TC157-TC161 checks and
|
||||
the affected final build/load/mount regressions recorded here.
|
||||
Reference in New Issue
Block a user