This commit is contained in:
2026-08-18 09:20:44 +02:00
commit b826cd721a
522 changed files with 93730 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# Manual Test Evidence
## Final Status
repo22 has 161 executable manual test cases, TC001-TC161, plus the non-executable
TC000 template. The canonical dated reports produce:
| Status | Count |
| --- | ---: |
| PASS | 160 |
| PARTIAL | 1 |
| FAIL / KERNEL-FAIL | 0 |
| ENVIRONMENT-UNAVAILABLE | 0 |
| SHELVED test case | 0 |
TC146 is the single PARTIAL case. Its HEAD2 and interlaced paths pass, while
the explicit mapped-payload positive fixture remains unavailable. That
shelved subitem is not counted as a second test case.
## Evidence Policy
Authoritative evidence consists of the numbered Markdown procedures,
deterministic field-asserting fixture helpers, native syscall/kernel probes,
and dated `tests/results/manual/*/manual-test-report.md` reports. Reports record
the exact source, module or fixture hashes, FreeBSD kernel behavior, errno, and
cleanup state.
Host-only fixture generation, static ABI review, and userspace erofs-utils
output are supporting evidence. They do not replace a required FreeBSD kernel
result. The suite is manual-only; no CI pipeline or automated kernel-test
harness is implemented or claimed.
## Canonical Runs
The final status uses these non-overlapping report groups for TC001-TC156:
| Group | Exact scope | Result |
| --- | --- | --- |
| G1 | 32 IDs | 32 PASS |
| G2 | 13 IDs | 13 PASS |
| G3 | 31 IDs | 31 PASS after the dated TC060 fixed-source rerun |
| G4 | 27 IDs | 27 PASS |
| G5 | 24 IDs | 23 PASS, TC146 PARTIAL |
| G6 | 11 IDs | 11 PASS |
| G7 | 11 IDs | 11 PASS |
| G8 | 7 IDs | 7 PASS |
The G1-G8 tables contain exactly 156 rows and 156 unique IDs, with no missing
or duplicate TC from TC001-TC156. TC060's original G3 KERNEL-FAIL is historical
and is superseded by
`tests/results/manual/2026-08-09T1120Z-tc060-fix/manual-test-report.md`.
TC153 passed in the final G3 run and its issue is resolved.
TC157-TC161 are recorded in
`tests/results/manual/2026-08-09T1804Z-final-review-independent/manual-test-report.md`.
All five pass on exact source baseline
`fcc85b93d5f8fd9671686bd68bf3b086c8bd25cf`.
## TC111 Coverage Audit
A bounded audit of `tests/TC[0-9][0-9][0-9]-*.md` found 162 rows and 162 unique
IDs, TC000-TC161, with no missing or duplicate ID. Excluding TC000 leaves 161
executable specifications.
The audit also cross-checked the eight canonical group tables and the five
final-review rows. Final arithmetic is `155 PASS + 1 PARTIAL + 5 PASS`, or
160 PASS and one PARTIAL.
## Final Build Qualification
The final independent run rebuilt both configurations after the TC161 failing
`nm` shim and used `/usr/bin/nm` for the post-shim build:
| Configuration | KLD SHA256 |
| --- | --- |
| `WITH_ZSTDIO=0` | `15fda9d334132cd81769ce4dff4f8411a6e2b4cf7531c352530d0de85f42a2d2` |
| `WITH_ZSTDIO=1` | `23782dc0ce7da188807d35020bf2d8c6044b796c5c9a8746b398ba784cd6ad4e` |
Both KLDs loaded, mounted a qualified image read-only, unmounted, detached the
md provider, and unloaded. Final guest mount, md, EROFS KLD, and DTrace KLD
counts were zero.
G1-G8 evidence was collected across multiple source commits. It is not claimed
that all historical tests ran on the final KLD. The final code baseline was
independently exercised by TC157-TC161 and the affected dual-build,
module-load, and mount smoke.
## Issue Status
- TC010: RESOLVED by a real 16 TiB-plus sparse-provider statfs run.
- TC060: RESOLVED by the FreeBSD pathconf fix and exact-source rerun.
- TC153: RESOLVED by the multi-TiB Layout 0 directory lookup run.
- Explicit mapped payload: SHELVED as a detailed fixture/tooling limitation;
TC146 remains PARTIAL.
See `issues/README.md` for the current index and the individual files for
triggers, analysis, attempts, results, feature impact, and acceptance criteria.
+283
View File
@@ -0,0 +1,283 @@
# repo22 架构设计
## 设计目标
repo22 提供尽量贴近 Linux `fs/erofs` 职责划分的 FreeBSD 15
只读实现,同时对 FreeBSD vnode、GEOM、pager、`dev_t` 和 NFS FID 语义做
必要适配。
## 核心原则
1. **Linux 对齐**:函数命名、文件组织、代码排序尽可能匹配 Linux 版本
2. **最小抽象**:避免不必要的封装层
3. **安全优先**:保留 repo19 的所有安全修复
4. **可维护性**:便于社区维护和与上游同步
## 文件组织
```
src/
├── super.c - 超级块、挂载、VFS 集成
├── inode.c - inode 读取和 vnode 管理
├── data.c - 数据块映射和解压缩
├── namei.c - 路径查找(二分搜索)
├── dir.c - 目录遍历和输出
├── xattr.c - 扩展属性和 ACL
├── erofs_vnops.c - VFS vnode 操作实现
├── decompressor.c - 压缩配置解析和统一调度
├── decompressor_lz4.c - FreeBSD 有界 LZ4 后端
├── decompressor_lzma.c - MicroLZMA 后端
├── decompressor_deflate.c - DEFLATE 后端
├── decompressor_zstd.c - 可选 ZSTDIO 后端
├── zmap.c - 压缩逻辑块映射
├── zdata.c - 压缩数据读取
├── internal.h - 内存结构和内部 API
├── erofs_fs.h - 磁盘格式定义
└── xattr.h - 扩展属性接口
```
## 分层架构
```
┌─────────────────────────────────────┐
│ VFS 层 (FreeBSD kernel) │
└─────────────┬───────────────────────┘
┌─────────────▼───────────────────────┐
│ VFS 接口层 │
│ - erofs_vnops.c │
│ - super.c (mount/unmount/root) │
└─────────────┬───────────────────────┘
┌─────────────▼───────────────────────┐
│ 文件系统逻辑层 │
│ - inode.c (erofs_read_inode) │
│ - namei.c (erofs_namei) │
│ - dir.c (erofs_readdir_block) │
│ - xattr.c (erofs_getxattr) │
└─────────────┬───────────────────────┘
┌─────────────▼───────────────────────┐
│ 数据访问层 │
│ - data.c (erofs_map_blocks) │
│ - data.c (erofs_read_data) │
│ - zmap.c / zdata.c │
│ - decompressor.c (z_erofs_decompress) │
└─────────────┬───────────────────────┘
┌─────────────▼───────────────────────┐
│ 块 I/O 层 │
│ - erofs_bread/erofs_brelse │
└─────────────────────────────────────┘
```
## 核心数据结构
### erofs_mount (内存中的文件系统状态)
```c
struct erofs_mount {
struct mount *mnt; // FreeBSD mount 结构
struct vnode *devvp; // 块设备 vnode
struct g_consumer *cp; // GEOM consumer
uint32_t block_size; // 块大小
uint64_t root_nid; // 根目录 NID
uint32_t feature_compat; // 特性标志
uint32_t feature_incompat;
struct erofs_sb_lz4_info lz4; // LZ4 参数
struct erofs_deviceslot *devs; // 设备表
struct erofs_xattr_prefix_item *xattr_prefixes; // xattr 前缀表
};
```
### erofs_node (内存中的 inode)
```c
struct erofs_node {
struct vnode *vnode; // 关联的 vnode
uint64_t nid; // 节点 ID
uint64_t size; // 文件大小
uint8_t datalayout; // 数据布局类型
// 压缩相关
uint8_t z_algorithmformat;
uint8_t z_lclusterbits;
// Chunk-based 相关
uint16_t chunkformat;
uint8_t chunkbits;
// Fragment 相关
uint32_t fragmentoff;
bool fragment;
};
```
## 关键实现细节
### 1. 数据布局支持
支持 4 种数据布局:
- **FLAT_PLAIN**: 连续块
- **FLAT_INLINE**: 最后一个逻辑块位于 inode metadata block 内,且受声明
image/metabox bounds 约束
- **CHUNK_BASED**: 固定大小 chunk,支持稀疏文件
- **COMPRESSED**: 可变大小压缩 cluster
### 2. 压缩算法
支持 4 种压缩算法及未压缩 transform:
- LZ4 (LZ4HC)
- LZMA
- DEFLATE
- ZSTD
- 未压缩
### 3. 高级特性
-**ztailpacking**: 压缩文件尾部内联
-**fragments**: 已验证的 fragment-backed 压缩文件与 metabox carrier
- ⚠️ **dedupe**: 仅声明已验证的 fragment/partial-reference 形式,不宣称
覆盖所有未来编码
-**xattr_prefixes**: 共享 xattr 前缀表
-**device_table**: 多设备支持
-**metabox**: 每 inode 元数据盒
### 4. 安全机制
repo22 的边界策略:
- 所有指针操作前检查边界
- 所有算术运算检查溢出
- 所有分配检查大小合理性
- 递归深度限制
- 设备 ID 和块地址验证
## 与 Linux 版本的差异
### 对照基线
本轮维护逐文件对照工作区中的 `/work/dev-src-linux/fs/erofs`。该目录是导入的
Linux 7.1-rc1 EROFS 参考快照;对照不依赖 repo22 与 Linux 树具有共同 Git
历史。`/work/linux-src/fs/erofs` 中对应文件与该精简快照字节一致,但不是本轮
文件映射的依据。
### 必要差异(FreeBSD 适配)
1. **内存分配**:使用 `malloc(..., M_EROFS, ...)` 而非 `kmalloc()`
2. **块 I/O**:通过 GEOM consumer 和 FreeBSD vnode/buffer 接口读取 provider
3. **VFS 接口**`erofs_vnops.c` 实现 FreeBSD `vop_vector`,不照搬 Linux
`inode_operations`、folio 或 iomap 接口
4. **错误约定**:内核入口返回正的 FreeBSD errnoLinux 负 errno 或
`ERR_PTR` 仅作为算法对照,不能机械移植
5. **压缩后端**BSD 调度器跨编译单元调用 `internal.h` 中的简单后端 API
Linux 使用 `struct z_erofs_decompressor` 和不同的内存/页面生命周期
6. **LZ4 文件职责**BSD 保留独立 `decompressor_lz4.c` 有界解码器;Linux LZ4 路径位于
`decompressor.c` 并依赖 Linux 内核 LZ4/page API
7. **平台特性**Linux `sysfs.c``fileio.c``fscache.c``ishare.c`
`zutil.c` 没有无条件对应物,不为文件外观引入空包装
8. **构建架构**:当前只验证 FreeBSD 15 amd64Makefile 明确拒绝其他
`MACHINE_ARCH`
### 保持一致的部分
- 静态目录 helper 使用 Linux 名称 `find_target_dirent`
- LZMA、DEFLATE、ZSTD 后端使用 Linux 文件名 `decompressor_*.c`
- Makefile 先列 metadata/VFS 文件,再列压缩调度、映射和后端文件
- 跨文件后端声明集中在 `internal.h`,不在调用方手写 `extern`
- `erofs_fs.h` 的磁盘格式定义和核心目录/映射算法按 Linux 语义核对
## 代码规范
### 命名约定
- 公共函数:`erofs_<module>_<action>`
- 静态函数:描述性名称,无固定前缀
- 宏:`EROFS_*` 全大写
- 结构体:`struct erofs_*`
### 函数排序(每个文件)
1. 辅助函数(static
2. 核心逻辑函数
3. VFS 接口函数
4. 模块注册/清理(仅 super.c)
### 错误处理
```c
int erofs_function(...)
{
int error = 0;
void *buf = NULL;
// 操作...
if () {
error = EINVAL;
goto fail;
}
// ...
return 0;
fail:
if (buf)
erofs_brelse(buf);
return error;
}
```
## 测试策略
### 单元测试
不存在受支持的用户态单元测试入口。旧的 `src/Makefile.test`
`tests/test_decompress.c``tests/test_decompress_standalone.c` 尝试把内核解压
源码按不匹配的用户态 ABI 链接;这些入口均已退役并删除,不能作为可运行测试或
feature 验证证据。
旧的 `test_super.c``test_inode.c` 只重复了测试文件中的公式,并未调用
内核生产解析路径,其中 inode harness 还引用过已删除的磁盘字段。它们已退役,
不得作为 feature 验证证据。superblock、inode、pager 和错误路径必须使用
`TC*.md` 中的确定性镜像,经 FreeBSD 内核模块实际挂载或访问验证。
### 集成测试
仓库根目录遗留的 `test_all_decompress.sh``test_chunk_based.sh` 包含其他
repo 的硬编码路径,不能作为 repo22 的测试入口或验收证据。受支持的验证方式是
直接执行 `tests/TC*.md` 中记录的 FreeBSD 15 内核步骤,并将命令、errno、哈希和
清理状态写入 `tests/results/manual/` 下的日期报告。
### VM 测试
- 挂载真实镜像
- 文件读取验证
- 性能基准测试
## 维护指南
### 同步上游 Linux 变更
1. 选定明确的 Linux `fs/erofs` 快照;当前工作区基线为
`/work/dev-src-linux/fs/erofs`
2. 逐文件识别修改,不假设两个实现共享提交历史
3. 检查是否为磁盘格式变更(`erofs_fs.h`)或 Linux 专属 VFS/page API
4. 只移植语义上适用的算法,并保留 FreeBSD errno、锁、GEOM 和 vnode 约定
5. 运行双配置构建、模块加载和真实镜像挂载测试
### 添加新特性
1.`erofs_fs.h` 添加磁盘格式定义
2.`internal.h` 添加内存结构
3. 实现解析逻辑(data.c/inode.c
4. 添加确定性 fixture 和对应的 `TC*.md` 内核测试
5. 更新文档
## 性能考虑
- **零拷贝**:直接从缓冲区缓存读取
- **延迟加载**:仅在需要时读取 inode 元数据
- **缓存友好**:利用 FreeBSD 的 vnode 缓存
- **批量操作**:目录读取一次性处理多个条目
## 已知限制
- 不支持写操作(只读文件系统)
- 不支持 FUSE 模式
- 构建和运行时验证目前仅覆盖 FreeBSD 15 amd64
- 不实现 Linux file-backed、fscache、page-cache sharing 或 sysfs 控制面
+65
View File
@@ -0,0 +1,65 @@
# repo-pre-2 Capability Record
## Status Vocabulary
This document separates inherited pre1 claims from fresh pre2 validation:
- `INHERITED CLAIM`: documented by the unchanged pre1 snapshot; not re-tested
for pre2.
- `STATIC GATE`: visible in tracked source or build declarations; runtime
behavior was not exercised.
- `NOT IMPLEMENTED`: explicitly excluded by inherited project documentation.
- `NOT CLAIMED`: inherited documentation deliberately limits the support
claim.
- `NOT RUN`: no build or runtime validation was performed for repo-pre-2.
`INHERITED CLAIM` is provenance, not a new PASS result. Existing reports under
`tests/results/manual/` describe earlier pre1 work and must not be cited as a
fresh repo-pre-2 execution.
## Snapshot Evidence
```text
repo-pre-1 source tree: 6a5a1a49d4a4e08f6ef1155de240500e63a15365
repo-pre-2 initial tree: 6a5a1a49d4a4e08f6ef1155de240500e63a15365
src-linux reference tree: b79b9a8b62f633e887a8b99075ff9412fabd7f83
runtime validation: NOT RUN
```
The inherited claims below are transcribed at category level from `README.md`
and `docs/features.md`. They are not an independent feature review.
## Capability Matrix
| Area | Inherited pre1 claim or static declaration | Pre2 status |
| --- | --- | --- |
| Mount and metadata | Read-only mount/unmount, `statfs`, superblock checksum and bounds, compact/extended inodes, 48-bit fields | INHERITED CLAIM; NOT RUN |
| Plain data | Flat plain, flat inline, tail bounds, and logical block accounting | INHERITED CLAIM; NOT RUN |
| Chunk and devices | Chunk indexes, holes, device tables, explicit devices, and flatdev mapping | INHERITED CLAIM; NOT RUN |
| Compressed data | LZ4, MicroLZMA, DEFLATE, full/compact indexes, partial references, HEAD2, ztailpacking, and qualified fragment forms | INHERITED CLAIM; NOT RUN |
| ZSTD | Source is always listed; `WITH_ZSTDIO=1` adds `ZSTDIO`, while the default is `0` | STATIC GATE; runtime NOT RUN |
| Directories and vnode operations | Lookup, readdir, cookies, namecache, access, readlink, pathconf, pager integration, and read-only mutation rejection | INHERITED CLAIM; NOT RUN |
| Xattrs and ACLs | Inline/shared namespaces, metabox storage, long/packed prefixes, and POSIX ACL reads | INHERITED CLAIM; NOT RUN |
| NFS export | 64-bit NIDs, generation handling, and malformed/stale handle validation | INHERITED CLAIM; NOT RUN |
| Build target | `src/Makefile` rejects architectures other than `amd64` | STATIC GATE; build NOT RUN |
| Write support | Writable filesystem operations | NOT IMPLEMENTED |
| `VOP_BMAP` | Explicitly unsupported in inherited documentation | NOT IMPLEMENTED |
| Complete future-format parity | All future incompat features and every dedupe encoding | NOT CLAIMED |
| Explicit compressed-extent positive payload | Inherited documentation records only partial coverage | NOT CLAIMED; NOT RUN |
| Performance guarantees | Manual observations are not a performance contract | NOT CLAIMED |
## Interpretation Rules
1. Source presence, feature-bit definitions, or build selection do not prove a
runtime capability.
2. A pre1 manual report does not become a pre2 PASS merely because the initial
trees match.
3. Phase 1 mechanical edits must not expand or reduce this capability matrix.
4. A future status change requires recorded evidence from an actually executed
validation step.
## Current Validation Declaration
No build, QEMU execution, test script, smoke test, mount, malformed-image
probe, or performance measurement was run for this initialization. The current
repo-pre-2 runtime validation result is therefore `NOT RUN` in every category.
+271
View File
@@ -0,0 +1,271 @@
.\" Copyright (c) 2026
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd August 9, 2026
.Dt EROFS 5
.Os
.Sh NAME
.Nm erofs
.Nd Enhanced Read-Only File System
.Sh SYNOPSIS
To mount an
.Nm
volume:
.Bd -literal -offset indent
mount -t erofs /dev/da0 /mnt
.Ed
.Sh DESCRIPTION
The
.Nm
driver provides read-only support for the Enhanced Read-Only File System
(EROFS), a modern compressed read-only filesystem designed for space
efficiency and performance.
EROFS is widely used in Linux distributions and mobile systems for root
filesystems, firmware images, and container layers.
.Pp
The
.Fx
implementation supports multiple compression algorithms, various data layouts,
extended attributes, and multi-device configurations.
.Pp
The currently qualified build target is
.Fx 15
on
.Sy amd64 .
The module Makefile rejects other architectures because they have not been
validated against this implementation's kernel ABI.
.Sh FEATURES
.Ss Inode Types
The
.Nm
driver supports both compact and extended inode formats:
.Bl -bullet -compact
.It
Compact inodes (32 bytes) for typical files
.It
Extended inodes (64 bytes) with extended metadata
.It
Special handling for single-link files
.It
Inline data (tailpacking) for small files
.El
.Ss Data Layouts
.Bl -bullet -compact
.It
.Sy FLAT_PLAIN :
Uncompressed contiguous data
.It
.Sy FLAT_INLINE :
Uncompressed data with inline tail
.It
.Sy Chunk-based :
Fixed-size chunks for multi-device support
.It
.Sy Compressed :
LZ4, DEFLATE, zstd, or LZMA compressed data with pcluster mapping
.El
.Ss Compression Algorithms
.Bl -tag -width "MicroLZMA"
.It Sy LZ4
Fast decompression for general-purpose use, including ztailpacking
(compressed tail in inode metadata)
.It Sy DEFLATE
Standard compression with good compression ratio
.It Sy zstd
High compression ratio when the module is built with
.Va WITH_ZSTDIO=1 .
ZSTD support is disabled by default
.Pq Va WITH_ZSTDIO=0 ,
and an enabled module requires a kernel built with
.Cd "options ZSTDIO" .
.It Sy LZMA/MicroLZMA
Maximum compression ratio for space-constrained environments
.El
.Ss Extended Attributes
.Bl -bullet -compact
.It
Shared extended attributes with metabox container support
.It
Inline extended attributes
.It
Long-prefix extended attribute support
.It
Packed prefix table support
.It
POSIX ACL support (access and default ACLs)
.El
.Pp
User namespace attributes are exposed without prefix; system namespace
attributes retain their full qualified names (trusted.*, security.*).
.Pp
Linux POSIX access/default ACL xattrs are decoded into
.Fx
POSIX.1e ACLs when present.
ACL and xattr mutation remains read-only.
.Ss Advanced Features
.Bl -bullet -compact
.It
Superblock CRC32C verification
.It
48-bit block count and root nid support
.It
Device table for multi-device volumes
.It
Qualified fragment-backed compressed files and metabox carriers
.It
VFS hash integration and namecache support
.It
Directory entry optimization (dot_omitted handling)
.It
NFS export with stable superblock-seeded per-inode file-handle generations
.It
.Fx
local vnode pager support for read-only mappings
.El
.Sh MOUNT OPTIONS
The
.Nm
filesystem supports standard read-only mount options and the following
filesystem-specific option:
.Bl -tag -width "device.N=/dev/mdN"
.It Cm device.N= Ns Pa path
Map one-based on-disk external device slot
.Ar N
to the disk provider at
.Ar path .
The slot number is part of the option name, so option order has no effect.
Every declared external slot must be supplied exactly once when external blob
providers are used.
Reusing the primary provider or one external provider for
multiple slots is rejected.
.El
.Pp
There is no filesystem-specific
.Pa /sbin/mount_erofs
utility in this repository.
Use the generic
.Xr mount 8
frontend with
.Fl t Cm erofs ;
it passes the filesystem-specific option names to
.Xr nmount 2 .
.Pp
If the image has a device table and no
.Cm device.N
options are supplied, the primary provider is treated as a flatdev image.
It
must contain every declared device range at its on-disk unified block address.
The driver forces every successful mount read-only.
An explicit
.Fl o Cm rw
request therefore still produces a read-only mount; it does not enable writes
and is not rejected solely because
.Cm rw
was requested.
Mutating vnode operations fail with
.Er EROFS .
.Sh EXAMPLES
Mount an EROFS image from a disk device:
.Bd -literal -offset indent
mount -t erofs -o ro /dev/da0s1 /mnt
.Ed
.Pp
Mount an EROFS image from a regular file using
.Xr mdconfig 8 :
.Bd -literal -offset indent
mdconfig -a -t vnode -f rootfs.img -u 0
mount -t erofs -o ro /dev/md0 /mnt
.Ed
.Pp
Mount a split image with two external blob slots.
The deliberately reversed
option order demonstrates that slot mapping is deterministic:
.Bd -literal -offset indent
mdconfig -a -t vnode -f primary.img -u 90
mdconfig -a -t vnode -f blob1.img -u 91
mdconfig -a -t vnode -f blob2.img -u 92
mount -t erofs -o ro -o device.2=/dev/md92 \
-o device.1=/dev/md91 /dev/md90 /mnt
.Ed
.Pp
Mount a flatdev image containing the primary image followed by all declared
unified device ranges:
.Bd -literal -offset indent
mdconfig -a -t vnode -f combined-flatdev.img -u 90
mount -t erofs -o ro /dev/md90 /mnt
.Ed
.Pp
Unmount an EROFS filesystem:
.Bd -literal -offset indent
umount /mnt
.Ed
.Sh DIAGNOSTICS
Error messages are logged via
.Xr printf 9
when filesystem inconsistencies are detected, such as:
.Bl -bullet -compact
.It
Invalid superblock magic number
.It
Superblock CRC32C checksum mismatch
.It
Unsupported compression algorithm
.It
Invalid inode format
.It
Invalid or unrepresentable inode timestamps
.It
Corrupted directory entries
.It
Compressed extent address arithmetic overflow
.It
Inline data crossing its inode metadata block or declared backing bounds
.It
Missing, short, or orphaned external providers
.It
Malformed or overlapping device-table ranges
.El
.Sh SEE ALSO
.Xr nmount 2 ,
.Xr mdconfig 8 ,
.Xr mount 8 ,
.Xr umount 8 ,
.Xr printf 9
.Sh HISTORY
EROFS was originally developed for Linux by Huawei in 2019.
The
.Fx
implementation first appeared in 2026.
.Sh AUTHORS
.An Ruicheng Pan
.Sh BUGS
.Bl -bullet -compact
.It
The driver is read-only and does not claim support for every future EROFS
incompat feature or every dedupe encoding.
.It
There is no automated kernel regression harness; the repository records
.Fx 15
manual-test procedures and results.
.El
+77
View File
@@ -0,0 +1,77 @@
# EROFS Feature Status
This list describes behavior implemented and manually qualified in the
FreeBSD 15 module. It is not a claim of complete Linux EROFS feature parity.
## Filesystem and Metadata
- Read-only mount/unmount and `statfs`.
- Superblock CRC32C, declared image/media bounds, 48-bit block/root-NID decode.
- Compact and extended inodes.
- Correct compact non-directory `I_NLINK_1` semantics and directory
`dot_omitted` semantics.
- Linux `new_decode_dev` major/minor decoding followed by FreeBSD `makedev()`.
- Superblock-seeded, inode-metadata-derived vnode/NFS generation synchronized
with `va_gen`; metadata-identical payload replacement is outside the stale
handle guarantee.
- NFS export, 64-bit NID file handles, stale-generation and malformed-FID
validation.
## Data Layouts
- `FLAT_PLAIN` and `FLAT_INLINE` reads.
- Inline tails constrained to the inode metadata block and declared backing
bounds.
- Chunk-based files, chunk indexes, holes, device tables, explicit devices,
and flatdev mapping.
- LZ4, MicroLZMA, DEFLATE, and ZSTD compressed reads.
- Full and compact indexes, partial references, HEAD2/interlaced records,
ztailpacking, and fragment-backed compressed data used by qualified images.
- Compressed `va_bytes`/`st_blocks` from the complete 48-bit on-disk compressed
block count; plain, inline, and chunk files retain logical block rounding.
## Vnode and Directory Operations
- `vget`, root lookup, `vfs_hash`, namecache, and `vn_vget_ino` integration.
- `lookup`, `readdir`, stable restart cookies, cold nested lookup, and
Linux-compatible nonzero final-name padding.
- Shared strict validation for dirent arrays, `nameoff`, block bounds, and
illegal names.
- `getattr`, access checks, `readlink`, `pathconf`, and read-only mutation
rejection, including combined special-vnode setattr requests.
- FreeBSD 15 `vnode_pager_local_getpages` and compatible async pager entry;
`VOP_BMAP` remains explicitly unsupported.
## Xattrs and ACLs
- Inline and shared `user.*`, `trusted.*`, and `security.*` attributes.
- Shared attributes in primary metadata and metabox containers.
- Long-prefix and packed-prefix-table lookup.
- FreeBSD user/system namespace exposure and POSIX access/default ACL reads.
- Read-only xattr/ACL mutation behavior.
## Documentation and Validation
- `erofs(5)` manual page.
- Markdown manual tests TC001-TC161, with unresolved cases tracked in
`issues/`.
- FreeBSD 15 manual reports with fixture hashes and cleanup evidence.
- Final explicit-extent ordering, 48-bit allocation, special setattr,
`OFF_MAX` directory-cookie, and build-tool failure checks.
## Build Qualification
- FreeBSD 15 `amd64` is the only currently validated and accepted target;
`src/Makefile` rejects other architectures.
- ZSTD support is disabled by default (`WITH_ZSTDIO=0`). Opt-in builds use
`WITH_ZSTDIO=1` and require a kernel built with `options ZSTDIO`.
## Not Implemented or Not Claimed
- Writable filesystem operations.
- A CI pipeline or automated kernel-test harness.
- Positive mapped-payload execution of the explicit compressed-extent format;
TC146 remains PARTIAL and the fixture limitation is tracked under `issues/`.
- Blanket support for every future EROFS incompat feature or every dedupe
encoding; only the explicitly qualified fragment/metabox forms are claimed.
- Performance guarantees from the manual throughput observations.
+36
View File
@@ -0,0 +1,36 @@
# Pre10 Baseline
Pre10 was created as an exact tracked-tree snapshot of `repo-pre-9` before
any Pre10 planning or source changes.
## Source identity
- Repository HEAD: `bedc8dae477ba0e82a4a7c4f0de7ccdd41696004`
- `repo-pre-9` tree: `54b2845b80c33f05109af1fea84e962aca3a82d3`
- `repo-pre-9/src` tree: `e657e8a63b097b061670f75ca01947a568ef33d5`
## Copy verification
Before this baseline file was added, `repo-pre-10` matched `repo-pre-9` in:
- file and directory manifest;
- byte-for-byte file contents;
- file modes and timestamps;
- complete subtree tree hash;
- `src` subtree tree hash.
The copied tree contained no nested `.git` directory, image, binary build
artifact, temporary file, or file larger than 10 MiB.
## Pre9 validation reference
The final Pre9 manual QEMU smoke validation is recorded in
[`pre9-cache-final-manual-validation.md`](pre9-cache-final-manual-validation.md).
It reports successful KLD build/load and read-only mounts, correct LZMA
single-read and full-file hashes, successful concurrent LZMA reads, a passing
LZ4 regression read, and complete test resource cleanup.
## Pre10 state
No Pre10 source, build, configuration, or planning change has been made. This
file only records the snapshot baseline.
+96
View File
@@ -0,0 +1,96 @@
# Pre10 Batch A: Compatibility Cleanup and Ordering
## Scope and baseline
Batch A changes only `src/internal.h`, `src/data.c`, and `src/zmap.c`. It is a
source-only structural change based on commit
`b5d6f5ce0696407f9f1cb3f0a9f8cbab643eea49`. No QEMU or feature test was run.
The starting blobs were:
| Path | Blob |
| --- | --- |
| `src/internal.h` | `cfa15de12b73319322ebb33049a7f14b9eb5c271` |
| `src/data.c` | `fdc138b0dad12501d842bae124c8c020793dbc97` |
| `src/zmap.c` | `51170741a0fb5eced814db898c1a233e8a23088a` |
## Removed compatibility declarations
Repository-wide fixed-string searches covered all files below
`repo-pre-10`, including source, the Makefile, and documentation. The only
non-definition match was an old review report describing
`erofs_is_fileio_mode()` as dead code.
| Candidate | Source consumers | Result |
| --- | ---: | --- |
| `EROFS_SYNC_DECOMPRESS_AUTO` and related enum values | 0 | Removed |
| `EROFS_ZIP_CACHE_DISABLED` and related enum values | 0 | Removed |
| `struct erofs_buf` | 0 | Removed |
| `__EROFS_BUF_INITIALIZER` | 0 | Removed |
| `erofs_map_blocks.buf` | 0 | Removed with its dead type |
| `erofs_is_fileio_mode()` | 0 | Removed |
These declarations modeled Linux facilities that the synchronous contiguous-
buffer FreeBSD path does not implement. Their removal does not replace or
alter the mount-owned decoded LZMA extent cache introduced in Pre9.
## Declaration and definition order
`internal.h` now exposes explicit groups for buffer/device I/O, inode/vnode
lifecycle, directory operations, logical mapping/file data, compressed
mapping/data, compression configuration/backends, and VOP vectors. Existing
signatures are unchanged.
In `data.c`, the regular-file wrapper now precedes the symlink wrapper, matching
the header's file-data grouping. Both wrappers retain their original bodies
and continue to delegate to `erofs_read_uio()`.
In `zmap.c`, `z_erofs_read_extent()` now follows the overflow-safe extent
position helpers it consumes. The Linux-shared compressed-index and map
functions retain their existing relative order; FreeBSD-only explicit-extent
helpers remain one contiguous responsibility group.
The reordering was deliberately bounded by the existing static call topology:
- `erofs_check_device_range()` remains before `erofs_map_dev()`;
- `erofs_inline_tail_start()` and `erofs_map_blocks_chunk()` remain before
`erofs_map_blocks()`;
- `erofs_bread_device()` remains before `erofs_bread()` and
`erofs_read_physical()`;
- `erofs_read_uio()` remains before both public vnode read wrappers;
- compressed-index decode/load helpers remain before their callers;
- `z_erofs_map_blocks_fo()` and `z_erofs_validate_extent_table()` remain
before `z_erofs_fill_inode()`; and
- fill, full/explicit mapping, and sanity helpers remain before
`z_erofs_map_blocks_iter()`.
No extra static prototype was introduced. Broader movement of
`erofs_map_dev()` was rejected in this batch because matching Linux's exact
position would either violate the current dependency order or add a prototype
solely to support cosmetic movement.
## Preserved behavior
- No function signature, return value, allocation flag, lock operation,
`bread`/`brelse` ownership, mapping flag, or control-flow statement changed.
- No structure field used by runtime code moved or changed.
- The Pre9 decoded extent cache fields, initialization/finalization calls,
key, lock, and one-entry-per-mount policy are untouched.
- No Linux page, folio, bio, workqueue, XArray, shrinker, or compatibility
wrapper was introduced.
## Static validation
| Check | Result | Evidence |
| --- | --- | --- |
| Removed-symbol search | PASS | Every removed enum, type, initializer, helper, and `map_blocks.buf` access has zero source/Makefile matches. |
| Prototype uniqueness | PASS | Each of the 25 grouped cross-file declarations occurs exactly once in `internal.h`. |
| `erofs_read_file()` body | PASS | Parent and working-tree normalized hashes are both `acc4cb05f9a1eb4a0066f8e4d6f3e84c8a69cfb70c762233a61ab08e84836aef`. |
| `erofs_readlink_target()` body | PASS | Parent and working-tree normalized hashes are both `73cc37f21f0c66a2c6bb27d39cc9f93f38df1994c7d386dacb5efc7734aa6a89`. |
| `z_erofs_read_extent()` body | PASS | Parent and working-tree normalized hashes are both `9145b1220329a2d113c69971c94537a99467a7252f3ea564b695a4530ea8e11d`. |
| Changed-path allowlist | PASS | Only the three source files and this Batch A report changed. |
| `git diff --check` | PASS | No whitespace errors. |
| QEMU/build | NOT RUN | Reserved for final aggregate Pre10 validation. |
The final Pre10 guest build and smoke run are intentionally deferred to the
aggregate validation stage defined by `planning/pre10/validation.md`.
+108
View File
@@ -0,0 +1,108 @@
# Pre10 Batch B: Bounded Metadata Helpers
## Scope and baseline
Batch B is a source-only extraction based on commit
`3c7c774b296a2ee90578a78f372aad5fd2edbec6`. It changes only
`src/super.c`, `src/data.c`, and `src/inode.c`, plus this report. No build,
QEMU smoke, or feature test was run.
The extraction keeps ownership at the original FreeBSD lifecycle boundaries:
- `erofs_mountfs()` still owns mount allocation, the primary GEOM transfer,
extent-cache lifecycle, device scanning, internal inode setup, publication,
and the single `erofs_sb_free()` failure path.
- `erofs_map_dev()` still owns device selection, range and overflow checks,
flat-device behavior, and all `ENODEV`/`EINTEGRITY` returns.
- `erofs_vget()` still owns vnode allocation, locking, mount association,
hash insertion, construction state, publication, and failure cleanup.
## Superblock helper
`erofs_read_superblock()` now reads the on-disk superblock into a temporary
buffer, copies the fixed 144-byte structure to caller-owned stack storage,
releases the temporary buffer, and performs the existing validation and field
decode in the original order. It also retains the existing device-size,
checksum, generation-seed, metabox-NID, and compression-configuration checks.
The helper does not allocate or publish the mount, open extra devices, create
internal inodes, initialize xattrs, or own failure cleanup. `erofs_mountfs()`
still calls, in order:
```text
z_erofs_extent_cache_init
erofs_read_superblock
erofs_scan_devices
shared-EA/metabox combination check
erofs_init_packed_inode
erofs_init_metabox_inode
erofs_xattr_prefixes_init
mount publication and flag setup
```
All prior superblock outcomes remain at the same semantic boundary:
| Condition | Preserved result |
| --- | --- |
| read failure | underlying `erofs_bread()` error |
| invalid magic or block geometry | `EINVAL` |
| unsupported directory blocks or feature bits | `EOPNOTSUPP` |
| invalid xattr, timestamp, or metabox metadata | `EINTEGRITY` |
| invalid device geometry | existing `EINVAL`, `EINTEGRITY`, or `ENXIO` |
| checksum mismatch | `EINTEGRITY` |
| generation/config read failure | existing underlying error |
Copying the superblock before releasing its read buffer makes its lifetime
explicit. The copy remains available to `erofs_scan_devices()` and volume-name
publication, while checksum and compression configuration reads continue to
use their existing independent I/O helpers.
## Device-map helper
`erofs_fill_map_dev()` is a pure assignment helper for `m_em`, `m_dif`, and
`m_pa`. It has no branches, errors, allocation, I/O, or ownership effects.
The caller retains all explicit-device and unified-range selection, arithmetic
overflow checks, physical range validation, flat-device handling, and external
device availability checks. In particular:
- flat explicit mappings still use the primary device and add the unified
offset to the existing physical address;
- flat implicit mappings still retain the primary device and unified address;
- non-flat explicit mappings select the requested extra device only after all
validation succeeds; and
- non-flat implicit misses still return success with the initial primary map.
## Vnode helper
`erofs_fill_vnode()` sets only the inode-derived vnode type, FIFO operation
vector, and `VV_ROOT` flag. It runs after `erofs_read_inode()` succeeds and
immediately before `VSTATE_CONSTRUCTED`, while `erofs_vget()` still holds the
exclusive vnode lock.
Hash lookup/insertion, allocation, `insmntque()`, locking, race handling,
failure `vgone()`/`vput()`, shared-lock downgrade, and `*vpp` publication remain
in `erofs_vget()` in their original order.
## Known pre-existing risk
The `insmntque()` failure branch remains unchanged. FreeBSD's `insmntque()` may
reclaim and release the vnode on failure, while the existing EROFS branch then
accesses `vp->v_data`. This possible use-after-release is outside a helper-only
batch and was deliberately not mixed into this commit. Consequently, this
report does not claim that the pre-existing vnode cleanup path is correct.
## Static validation
| Check | Result | Evidence |
| --- | --- | --- |
| Changed-path allowlist | PASS | Only three allowed source files and this report changed. |
| Helper ownership | PASS | Each helper is `static` and has one direct caller. |
| Mount lifecycle order | PASS | Cache init, device scan, internal inode/xattr setup, publication, and `erofs_sb_free()` remain in `erofs_mountfs()`. |
| Device-map errors | PASS | All range, overflow, device-selection, `ENODEV`, and `EINTEGRITY` branches remain in `erofs_map_dev()`. |
| Vnode lifecycle order | PASS | `insmntque()`, hash insertion, inode read, construction, downgrade, and publication retain their order. |
| `git diff --check` | PASS | No whitespace errors. |
| Build/QEMU/feature test | NOT RUN | Reserved for later aggregate Pre10 validation. |
The pre-existing `insmntque()` concern is deferred; no new errno, logging,
ABI, Linux lifecycle facade, or Pre9 decoded-cache change was introduced.
+216
View File
@@ -0,0 +1,216 @@
# Pre10 Batch C: BSD Decompressor Request Dispatch
Status: **STATIC PASS; RUNTIME NOT RUN**
This batch aligns the BSD decompressor boundary with the Linux source shape
where the responsibility is equivalent. It keeps the FreeBSD implementation
synchronous and contiguous-buffer based. No page, folio, bio, workqueue,
XArray, shrinker, or asynchronous decompression abstraction was added.
## Baseline and Scope
The implementation starts from Pre10 commit `d3c1cb90` and preserves Batch A,
Batch B, and the documented `insmntque()` lifecycle issue. The source changes
are limited to:
```text
repo-pre-10/src/internal.h
repo-pre-10/src/decompressor.c
repo-pre-10/src/lz4.c
repo-pre-10/src/decompressor_lzma.c
repo-pre-10/src/decompressor_deflate.c
repo-pre-10/src/decompressor_zstd.c
```
`zdata.c` was inspected but did not need a mechanical edit: its existing call
to `z_erofs_decompress()` remains the sole dispatcher call, and its buffer
release and decoded LZMA extent-cache publication remain unchanged.
## Request Contract
The new request is stack-owned by `z_erofs_decompress()` and is valid only for
the synchronous callback invocation:
```c
struct z_erofs_decompress_req {
struct erofs_mount *em;
const struct erofs_map_blocks *map;
const void *in;
size_t inputsize;
void *out;
size_t outputsize;
bool partial_decoding;
};
```
Field mapping is direct:
| Request field | Previous source | Ownership |
| --- | --- | --- |
| `em` | `em` / codec configuration arguments | Borrowed mount, never retained |
| `map` | `map` and its algorithm/offset fields | Borrowed mapping, never retained |
| `in` | `src` after padding removal | Borrowed compressed buffer |
| `inputsize` | `srclen` after padding removal | Value copy |
| `out` | `dst` | Borrowed decoded buffer |
| `outputsize` | `dstlen` | Value copy |
| `partial_decoding` | `partial` | Value copy |
The request does not own either data buffer. `zdata.c` continues to release
the compressed buffer with `erofs_brelse()`, and continues to free or publish
the decoded allocation after the callback returns.
The descriptor callback is intentionally synchronous:
```c
struct z_erofs_decompressor {
const char *name;
int (*config)(struct erofs_mount *,
const struct erofs_super_block *, const void *, size_t);
int (*decompress)(const struct z_erofs_decompress_req *);
};
```
The configuration callback receives the superblock argument because the LZ4
legacy configuration path uses it. The other codec configuration callbacks
retain their previous inputs and explicitly ignore that additional argument.
## Descriptor Coverage
`decompressor.c` contains one static array indexed by the on-disk algorithm
number. The entries are:
```text
0 LZ4 config + z_erofs_lz4_decompress
1 LZMA config + z_erofs_lzma_decompress
2 DEFLATE config + z_erofs_deflate_decompress
3 ZSTD config + z_erofs_zstd_decompress
4 SHIFTED plain transform callback
5 INTERLACED plain transform callback
```
The first four entries cover every real EROFS compression algorithm currently
defined by `erofs_fs.h`. The two runtime-only entries cover the existing plain
mapping forms. No duplicate backend entry point or compatibility wrapper is
present.
Configuration parsing preserves the old ordering:
1. Read the configuration record and its payload.
2. Return the read error immediately if either read fails.
3. Select the descriptor and invoke its configuration callback.
4. Release the payload with `erofs_brelse()`.
5. Return the callback's original error unchanged.
This retains the existing I/O-error priority. Unknown on-disk algorithm bits
are rejected before configuration reads by the existing
`Z_EROFS_ALL_COMPR_ALGS` mask check. A supported algorithm whose configuration
is unavailable returns `EOPNOTSUPP` from its existing callback. In particular,
the no-`ZSTDIO` build still emits the existing mount error and returns
`EOPNOTSUPP`.
## Decode and Error Semantics
The old and new paths have the following equivalent behavior:
| Condition | Result before Batch C | Result after Batch C |
| --- | --- | --- |
| Shifted/interlaced output larger than input | `EINTEGRITY` | `EINTEGRITY` |
| Shifted/interlaced transform success | `0` | `0` |
| Invalid algorithm format | `EOPNOTSUPP` | `EOPNOTSUPP` |
| Required zero-padding absent | `EINTEGRITY` | `EINTEGRITY` |
| LZMA dictionary not configured | `EINTEGRITY` | `EINTEGRITY` |
| Backend success | `0` | `0` |
| Any backend failure | `EIO` | `EIO` |
Padding removal remains in the dispatcher and is performed before the backend
request is updated. The LZ4 zero-padding exception remains unchanged. The LZMA
dictionary check remains before callback dispatch. Backend-specific checks are
unchanged apart from reading their previous parameters from the request or
mount configuration:
- LZ4 preserves literal/match bounds, overlap copying, partial completion,
and trailing-zero validation.
- MicroLZMA preserves input/output size limits, dictionary selection, decoder
shutdown, full-stream consumption, and partial output acceptance.
- DEFLATE preserves window validation, `inflateEnd()`, no-progress detection,
output completion, and full-stream input consumption.
- ZSTD preserves window selection, decoder destruction on every initialized
path, no-progress detection, output completion, and full-stream consumption.
## Consumer and Symbol Proof
Before the change, repository searches found exactly one in-tree consumer of
each old backend symbol: the switch in `decompressor.c`. The only consumer of
`z_erofs_decompress()` is `zdata.c`. After the change:
```text
old lz4_decompress 0 definitions/references
old lzma_decompress 0 definitions/references
old deflate_decompress 0 definitions/references
old zstd_decompress 0 definitions/references
new LZ4 backend one definition, one descriptor reference, one prototype
new LZMA backend one definition, one descriptor reference, one prototype
new DEFLATE backend one definition, one descriptor reference, one prototype
new ZSTD backend two definitions for #ifdef/#else, one descriptor reference,
one prototype
```
The two ZSTD definitions are mutually exclusive build branches, not duplicate
runtime implementations. No old-name wrapper was retained because no real
consumer remains.
## Cache and Ownership Review
The Pre9 decoded LZMA cache policy is untouched. The request callback returns
before the cache code runs, so the following remain owned by `zdata.c`:
- compressed-buffer release;
- decoded allocation cleanup on failure;
- decoded extent publication;
- duplicate-cache replacement and old-entry freeing;
- one-entry-per-mount bound and cache lock lifecycle.
No callback stores the request pointer or either borrowed buffer after return.
## Static Checks
The following checks were run before this report was written:
```sh
git diff --check
git diff --name-only | sort
grep -RInE '(^|[^_])(lz4_decompress|lzma_decompress|deflate_decompress|zstd_decompress)\\(' repo-pre-10/src
grep -RInE 'z_erofs_(lz4|lzma|deflate|zstd)_decompress' repo-pre-10/src
grep -RInE '\\b(page|folio|bio|workqueue|xarray|shrinker)\\b' \\
repo-pre-10/src/internal.h repo-pre-10/src/decompressor.c \\
repo-pre-10/src/lz4.c repo-pre-10/src/decompressor_lzma.c \\
repo-pre-10/src/decompressor_deflate.c repo-pre-10/src/decompressor_zstd.c \\
repo-pre-10/src/zdata.c
```
Results:
- `git diff --check`: PASS.
- Changed paths: only the six allowed source files: PASS.
- Old backend symbol search: no matches: PASS.
- New backend definitions and descriptor references: PASS.
- Forbidden Linux memory-model concepts in the changed codec boundary: no
matches: PASS.
- Descriptor entries: six unique designated entries covering algorithms 0-5:
PASS.
- `zdata.c` cache and ownership diff: unchanged: PASS.
The host is not a FreeBSD build environment, so this batch does not claim a
KLD build. Per the Pre10 instruction, QEMU smoke testing and the full feature
matrix were not run in this batch.
## Concerns and Deferred Validation
The primary remaining validation is a FreeBSD guest KLD build followed by the
planned Pre10 smoke run. That runtime work is intentionally separate from this
static implementation batch. Full feature tests remain outside Pre10.
The existing `erofs_vget()` `insmntque()` failure-path P1 remains documented
in `repo-pre-10/issues/erofs-vget-insmntque-failure-use-after-release.md` and
was not changed here.
+347
View File
@@ -0,0 +1,347 @@
# Pre10 Completion and Targeted Smoke Report
Status: **PRE10 IMPLEMENTATION COMPLETE; TARGETED SMOKE PASS; FULL FEATURE TEST NOT RUN**
Date: 2026-08-13
## Scope and goals
Pre10 is a controlled maintenance-alignment release based on the final Pre9
tree. Its purpose is to make the FreeBSD EROFS implementation easier to compare
and maintain alongside the independent Linux EROFS repository without copying
Linux-only lifecycle or memory-management models.
The planned work was limited to three source batches:
1. Remove unused Linux-shaped compatibility declarations and improve source
ordering.
2. Extract bounded helpers where Linux and FreeBSD responsibilities are
comparable, while retaining FreeBSD ownership and errno behavior.
3. Introduce a BSD-native synchronous decompressor request and descriptor
boundary with Linux-comparable codec names.
Full feature validation, CI execution, performance redesign, Linux page/folio/
bio/workqueue integration, and new format support were outside Pre10. This
report does not claim that every EROFS feature was tested.
## Repository history and identities
The relevant pushed commits are:
| Commit | Purpose |
| --- | --- |
| `cdfcd79640f252830c4e507f65aa7387eaa98ae0` | Snapshot `repo-pre-10` from the current `repo-pre-9`. |
| `b5d6f5ce0696407f9f1cb3f0a9f8cbab643eea49` | Add the Pre10 execution plan under `planning/pre10/`. |
| `3c7c774b296a2ee90578a78f372aad5fd2edbec6` | Batch A compatibility cleanup and ordering. |
| `8c246ea3c4dbf94b92d1f27a9d1b6b6eda582c31` | Batch B bounded metadata helper extraction. |
| `d3c1cb90b21ae8a97f549bc256d120268e5d09e9` | Record the discovered `insmntque()` vnode lifecycle issue. |
| `5c8a47e916b1521cb8a35ec4f546e5e16fb8c2dc` | Batch C decompressor request and descriptor alignment. |
| `9fbf0d7e24084d2342ecd61eff21592b1a83d225` | Fix `insmntque()` failure ownership. |
| `ff6586c2b24e155a3cbdf55b2ed80edb36d3e406` | Clarify the issue status after the source fix. This is the commit tested by the final smoke run. |
The snapshot report records the Pre9 source tree inherited by Pre10 as
`e657e8a63b097b061670f75ca01947a568ef33d5`. The final smoke runner independently
recorded these tested identities:
```text
commit=ff6586c2b24e155a3cbdf55b2ed80edb36d3e406
repo_pre_10_tree=3387e32efbcb0bec7e0bb57ecfc42daf9b962d28
src_tree=ec684d8dbd7662070234da1fe947aba48a293db8
```
Evidence: `/work/tests-dev/temp/pre10-smoke-final-20260813T102543Z/dut-identities.txt`.
## Batch A: compatibility cleanup and ordering
Batch A changed `src/internal.h`, `src/data.c`, and `src/zmap.c`.
Repository-wide consumer searches proved that the following declarations did
not represent implemented FreeBSD behavior and had no source, Makefile, ABI,
initializer, or field-access consumer:
- `EROFS_SYNC_DECOMPRESS_*`;
- `EROFS_ZIP_CACHE_*`;
- `struct erofs_buf` and `__EROFS_BUF_INITIALIZER`;
- the unused `erofs_map_blocks.buf` member; and
- `erofs_is_fileio_mode()`.
Removing these declarations improves maintainability because the FreeBSD tree
no longer advertises Linux mechanisms that it does not implement. The real
FreeBSD synchronous contiguous-buffer path and the Pre9 mount-owned decoded
LZMA extent cache remain explicit and unchanged.
Declarations and existing function definitions were grouped by actual
responsibility, closer to the Linux source's readable organization where the
responsibilities match. Static call topology was preserved, and moved function
bodies were compared for equivalence. No return value, allocation flag, lock,
mapping flag, `bread`/`brelse` ownership rule, or runtime structure field was
changed.
Detailed evidence: `repo-pre-10/docs/pre10-batch-a.md`.
## Batch B: bounded FreeBSD helper boundaries
Batch B changed `src/super.c`, `src/data.c`, and `src/inode.c`.
It extracted three `static` helpers with one direct caller each:
- `erofs_read_superblock()` isolates superblock read, fixed-size copy,
validation, field decode, checksum, generation seed, and compression
configuration parsing. `erofs_mountfs()` still owns mount allocation, GEOM
transfer, device scanning, internal inode setup, publication, and the single
mount cleanup path.
- `erofs_fill_map_dev()` performs only the final `m_em`, `m_dif`, and `m_pa`
assignments. Device selection, range checks, overflow checks, flat-device
semantics, and `ENODEV`/`EINTEGRITY` decisions remain in `erofs_map_dev()`.
- `erofs_fill_vnode()` performs only inode-derived vnode field setup. FreeBSD
vnode allocation, locking, mount association, hash insertion, race handling,
construction state, downgrade, publication, and cleanup remain in
`erofs_vget()`.
These boundaries improve Linux/FreeBSD visual comparability at the function
responsibility level while deliberately retaining the FreeBSD GEOM, vnode,
hash, lock, and cleanup contracts. Linux lifecycle names such as `iget` or
`fill_super` were not adopted where their semantics would be misleading.
Detailed evidence: `repo-pre-10/docs/pre10-batch-b.md`.
## Batch C: BSD decompressor request dispatch
Batch C changed `src/internal.h`, `src/decompressor.c`, `src/lz4.c`,
`src/decompressor_lzma.c`, `src/decompressor_deflate.c`, and
`src/decompressor_zstd.c`.
It introduced a stack-owned, synchronous, contiguous-buffer
`z_erofs_decompress_req` and a descriptor table covering LZ4, LZMA, DEFLATE,
ZSTD, SHIFTED, and INTERLACED. Codec entry points now use the Linux-comparable
names `z_erofs_lz4_decompress`, `z_erofs_lzma_decompress`,
`z_erofs_deflate_decompress`, and `z_erofs_zstd_decompress` because their
functional roles are equivalent.
The interface remains BSD-native:
- no page, folio, bio, workqueue, XArray, shrinker, asynchronous request, or
Linux ownership wrapper was introduced;
- request fields borrow the existing compressed and decoded buffers only for
the synchronous callback;
- `zdata.c` still owns buffer release and LZMA decoded-cache publication;
- plain SHIFTED/INTERLACED transforms remain in the existing dispatch model;
- LZ4 padding behavior, partial-decoding source, LZMA dictionary validation,
ZSTD conditional availability, and codec resource release remain unchanged;
- configuration I/O errors preserve priority and backend failures remain
normalized to the existing `EIO` result.
This gives third-party maintainers a recognizable request/descriptor and codec
naming shape without importing Linux kernel runtime assumptions.
Detailed evidence: `repo-pre-10/docs/pre10-batch-c.md`.
## P1 vnode lifecycle finding and fix
Independent Batch B review found a pre-existing P1 ownership violation in
`erofs_vget()`: after `insmntque()` returned an error, the old branch wrote
through `vp->v_data` even though FreeBSD may already have executed `vgone()`
and `vput()` and ended caller ownership of that vnode.
Commit `9fbf0d7e24084d2342ecd61eff21592b1a83d225` applied the minimal ownership
fix. The error branch now frees only the independently allocated `en`, clears
`*vpp`, returns the original error, and performs no further access, unlock,
reclaim, or release operation on `vp`. The successful path, vnode hash race,
lock state, errno, and later reclaim path were not changed.
Static ownership review confirms that the kernel failure path clears
`v_data`, installs `dead_vnodeops`, and owns vnode cleanup; dead vnode reclaim
does not release the filesystem's separately allocated `en`, so exactly one
caller-side `free(en, M_EROFS)` remains necessary.
The source defect is fixed, but runtime closure is still pending. Closing the
issue requires a dedicated mount/unmount race test that repeatedly creates or
looks up uncached vnodes while normal and forced unmount are attempted, with
kernel diagnostics capable of detecting stale vnode access, memory corruption,
lock misuse, and double release. The ordinary smoke test below does not trigger
or prove this teardown race.
Issue record:
`repo-pre-10/issues/erofs-vget-insmntque-failure-use-after-release.md`.
## Static review result
The final static review accepted Batches A, B, and C and the P1 fix. Checks
included changed-path allowlists, zero-consumer proof, prototype and backend
symbol uniqueness, descriptor coverage and bounds, normalized moved-function
body comparison, cleanup/ownership matrices, errno preservation, mount and
vnode lifecycle ordering, cache ownership, forbidden Linux mechanism searches,
conflict-marker searches, clean-worktree checks, `git diff --check`, and local
`HEAD`/`xdm/main` agreement at each pushed boundary.
No new P0 or P1 defect was found in the three planned batches. Static review is
not runtime proof. In particular, it does not close the dedicated vnode
teardown race test or the older explicit-extent positive-fixture gap recorded
under `repo-pre-10/issues/`.
## Initial inconclusive smoke attempts
Two isolated attempts preceded the final PASS. Neither is treated as a DUT
failure or a PASS.
### Attempt 1
Evidence directory:
`/work/tests-dev/temp/pre10-smoke-20260813T100326Z`.
The readiness probe began before the QEMU process had actually started, then
performed repeated SSH handshakes while the TCG guest was still becoming
responsive. Sources and fixtures were eventually transferred and the build log
reached the final module link/strip stage, but the long SSH build session ended
with transport status `255`. No reliable KLD SHA, `kldload`, or `kldstat`
evidence was produced.
The first runner incorrectly continued into the plain case after the failed
build/load stage. The guest reported `Invalid fstype`, while `results.txt`
incorrectly recorded `mount_status=0` because the exit status was not captured
immediately after `mount`. Since module loading was never proven and the
runner's status recording was unsound, the mount result is not attributable to
Pre10 and the attempt is **INCONCLUSIVE**, not FAIL or PASS.
### Attempt 2
Evidence directory:
`/work/tests-dev/temp/pre10-smoke-retry-20260813T101136Z`.
The fresh guest booted and returned a valid FreeBSD identity after intermittent
connection refusals, banner timeouts, and resets. The orchestration then exited
almost immediately. The claimed SCP failure was not backed by an actual
`scp-images.log`; only a failed attempt to tail that missing file remained.
No build, module load, mount, or hash result exists. This attempt is also
**INCONCLUSIVE** and provides no DUT verdict.
Both attempts stopped only their owned QEMU process, released ports `10040`
and `10041`, deleted their disposable overlays, preserved the immutable base,
and left guard PID `26318` and port `9222` unchanged.
The infrastructure diagnosis was recorded at
`/work/tests-dev/fix-todo/pre10-smoke-ssh-banner-timeout.md`. Its original
`PENDING_FIX` verdict describes those two attempts; the corrected final runner
below subsequently completed the required targeted smoke.
## Corrected fail-fast runner
The successful run used a new directory and port `10042`, a fresh disposable
overlay, 4096 MiB and two TCG CPUs, and a strictly ordered runner:
1. Verify exact Git, base-image, fixture, guard, and port identities before
starting QEMU.
2. Start QEMU and record its owned PID before beginning readiness checks.
3. Wait for TCP, require two consecutive successful FreeBSD SSH identity
checks, and establish one persistent SSH ControlMaster connection.
4. Transfer the source archive, guest scripts, and all fixtures in one batch,
then verify fixture hashes in the guest.
5. Run the guest build as a background job that writes `build.rc` and a
completion sentinel; poll the sentinel rather than depending on one long
SSH session.
6. Stop immediately unless the build return is zero, `erofs.ko` exists, its
hash is recorded, `kldload` succeeds, and `kldstat -n erofs` proves the
module is loaded.
7. Run each filesystem case separately and immediately record attach, mount,
mount-proof, hash, unmount, and detach return codes. A missing proof or
nonzero result stops the runner and cannot be converted to PASS.
8. Review guest cleanup and `dmesg`, unload the module, and prove it is absent.
9. Use the cleanup trap to stop only the recorded QEMU PID, release only the
run-owned port and overlay, and revalidate the immutable base and guard VM.
Runner evidence:
`/work/tests-dev/temp/pre10-smoke-final-20260813T102543Z/runner.sh` and
`runner.log`.
## Final targeted smoke result
Final evidence directory:
`/work/tests-dev/temp/pre10-smoke-final-20260813T102543Z`.
Overall result: **PASS** for the targeted Pre10 smoke scope.
### Isolation
- Immutable base image mode before and after: `0444`.
- Immutable base image SHA-256 before and after:
`67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef`.
- The `.bp` was not booted or modified directly; the run used a disposable
qcow2 overlay.
- Guard PID: `26318`; guard SSH port: `9222`.
- Guard command identity and `/proc` start value `1102996924` matched before
and after, and port `9222` remained reachable.
Evidence: `base-before.txt`, `cleanup-status.txt`, `guard-before.txt`,
`guard-command-before.txt`, `guard-command-after.txt`,
`guard-start-before.txt`, and `guard-start-after.txt` in the final evidence
directory.
### KLD build, load, and unload
- FreeBSD guest build return: `0`.
- Built KLD SHA-256:
`4ce2d44a0902502a40f07805606485c99869371b3cbab1c70ee19a8ac29d90f1`.
- The loaded module hash matched the built module hash.
- `kldstat` showed `erofs.ko` loaded as module ID `5`.
- `kldunload` succeeded, and the final `kldstat -n erofs` correctly reported
that no such loaded module remained.
Evidence: `build-result.out`, `build-result.rc`, `module-load.out`,
`module-load.rc`, and `guest-evidence.tar.gz`.
### Filesystem cases
| Case | Mounted file SHA-256 | Time | Result |
| --- | --- | ---: | --- |
| Plain `/data.txt` | `056f8f7585667dc695e2edf936deaf84cfee0f88671ba6cd5be22ce890763433` | `0s` | PASS |
| LZ4 `/compressed.bin` | `3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880` | `5s` | PASS |
| LZMA full `/level.dat` | `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461` | `2s` | PASS |
Each case recorded zero returns for md attach, EROFS mount, mount proof,
full-file hash, unmount, and md detach. The exact fixture image hashes were:
```text
plain image 3785ca07e7bd16f6c611191596ae0314253c0ae9b7217a4b22de25799b0f08ac
LZ4 image 967cc1b625546f9f9f881472e71cc3d849c65feb4e98e67fbfdc9b90a4be6dda
LZMA image 32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9
```
Evidence: `fixture-host-sha256.txt`, `results.txt`, and the per-case files in
`guest-evidence.tar.gz`.
### Kernel messages and cleanup
The post-test `dmesg` delta and suspect scan were empty: no EROFS mount,
integrity, decompression, panic, or assertion error was found. After all cases,
only the guest root filesystem and `devfs` remained mounted, no test md device
remained, and the EROFS module was unloaded.
Host cleanup also passed:
- the run-owned QEMU process stopped;
- port `10042` was released;
- the disposable overlay was deleted;
- the base image mode and hash were unchanged; and
- the guard VM remained unchanged and reachable.
Evidence: `dmesg-delta.txt`, `dmesg-suspect.txt`, `final-guest-state.out`,
`cleanup-status.txt`, and `guest-evidence.tar.gz`.
## Validation limits and conclusion
Pre10's planned source changes are complete, independently statically reviewed,
committed, pushed, built in a FreeBSD guest, and covered by the targeted plain,
LZ4, and LZMA smoke run. That targeted scope passed at tested commit
`ff6586c2b24e155a3cbdf55b2ed80edb36d3e406`.
No full feature test and no `/work/tests-dev/test_all.py` run was performed.
This report therefore does not claim all features, codecs, mapping layouts,
multi-device behavior, partial references, tailpacking, memory pressure, or
concurrent teardown behavior passed.
A feature-specific test is still required to close the fixed `insmntque()`
issue's runtime-validation status: the dedicated normal/forced mount-unmount
race with uncached vnode creation and kernel diagnostics described above. It
is desirable and required for issue closure, but it was not run because the
instruction for Pre10 was smoke testing only and explicitly excluded feature
testing.
+35
View File
@@ -0,0 +1,35 @@
# Pre11 Baseline
Pre11 was created as an exact snapshot copy of `repo-pre-10` before any
Pre11 planning or implementation changes.
## Source identity
- Repository source commit: `c191517f3799291852f8673e90b0f1be8427d3a9`
- `repo-pre-10` tree: `53d5c5fadc2d1fabf83346eadadec44c97c85c10`
- `repo-pre-10/src` tree: `ec684d8dbd7662070234da1fe947aba48a293db8`
## Destination identity before this document
- `repo-pre-11` tree: `53d5c5fadc2d1fabf83346eadadec44c97c85c10`
- `repo-pre-11/src` tree: `ec684d8dbd7662070234da1fe947aba48a293db8`
## Exact copy verification
Before this baseline document was added, `repo-pre-10` and `repo-pre-11`
were verified identical by:
- recursive byte-for-byte content comparison;
- complete file and directory manifest comparison;
- file type, mode, and symbolic-link target comparison;
- identical complete subtree tree IDs;
- identical `src` subtree tree IDs.
The snapshot contains the same 307 tracked files as `repo-pre-10`. No nested
Git repository, external untracked file, generated image, temporary artifact,
or file larger than 10 MiB was introduced by the copy.
## Pre11 state
No implementation, build, configuration, or behavior change has occurred.
This file only records the Pre11 snapshot baseline.
+10
View File
@@ -0,0 +1,10 @@
# Pre11 Batch A
This batch performs two mechanical vocabulary alignments:
- `struct erofs_mount.block_bits` is renamed to `blkszbits`.
- `erofs_fill_map_dev()` is renamed to `erofs_fill_from_devinfo()`.
No types, expressions, control flow, ownership rules, error handling, or runtime behavior were changed. Semantic type alias changes were deferred because they were not needed for this pure mechanical batch.
Validation is limited to identifier occurrence checks, diff inspection, and `git diff --check`. Build and runtime testing are deferred to the later Pre11 validation stage.
+49
View File
@@ -0,0 +1,49 @@
# Pre11 Batch B: Vnode Operation Scaffolding
## Scope
This batch is a behavior-preserving cleanup limited to vnode operation
scaffolding:
- removed the unused `EROFS_MOUNT_XATTR_USER` option bit;
- removed the zero-consumer `erofs_node.vnode` field and its only assignment;
- grouped VOP forward declarations and `erofs_vnodeops` initializers by
responsibility.
No function definitions, VOP registrations, lock or reclaim behavior,
`vnode_create_vobject()` error handling, or FIFO operations were changed.
## Consumer Audit
Before the edit, `EROFS_MOUNT_XATTR_USER` appeared only at its definition.
The `erofs_node.vnode` member appeared only in the structure definition and in
the single assignment performed by `erofs_vget()`; it had no read, address, ABI,
initializer, or offset consumer.
After the edit, both removed symbols have zero source-tree matches.
## VOP Vector Equivalence
Before reordering:
- `erofs_vnodeops`: 24 raw entries, 24 unique normalized `field=value` entries;
- `erofs_fifoops`: 14 raw entries, 14 unique normalized `field=value` entries.
After reordering:
- `erofs_vnodeops`: 24 raw entries, 24 unique entries, with the sorted
normalized set identical to the before snapshot;
- `erofs_fifoops`: 14 raw entries, 14 unique entries, with both the sorted set
and original entry sequence identical to the before snapshot.
## Validation
The batch uses static checks only:
- normalized before/after VOP vector set comparison;
- raw entry count versus unique entry count;
- removed-symbol zero-consumer search;
- changed-path whitelist;
- `git diff --check` and conflict-marker scan.
No build, QEMU smoke test, or feature test was run for this mechanical batch.
+99
View File
@@ -0,0 +1,99 @@
# Pre11 Batch C: ACL Parsing Responsibilities
## Scope
This batch mechanically separates POSIX ACL wire-format parsing from the
FreeBSD vnode and xattr acquisition path.
Modified files:
- `src/xattr.c`
- `docs/pre11-batch-c.md`
No xattr body loading, header-only xattr behavior, namespace handling, public
VOP interface, build configuration, or test infrastructure was changed.
## Responsibility Split
`erofs_get_acl()` continues to own:
- mount option and ACL type validation;
- the default-ACL directory restriction;
- no-ACL filter handling and mode fallback;
- xattr namespace and name selection;
- both `erofs_getxattr()` stages;
- FreeBSD `uio` and `iovec` setup;
- `ENOATTR` fallback and the stack input buffer.
The new `erofs_posix_acl_from_xattr()` receives an already-read buffer and
owns only wire-format validation and ACL population. It borrows both the input
buffer and output ACL and performs no allocation, release, lookup, or vnode
operation.
`erofs_acl_from_mode()` now receives the inode mode directly. This preserves
the original fallback writes while preventing the parser from depending on an
`erofs_node`.
## Moved-Code Mapping
The parsing block formerly at the end of `erofs_get_acl()` was moved in the
same statement order into `erofs_posix_acl_from_xattr()`:
1. Header length and entry-size remainder validation.
2. Header copy and version validation.
3. Entry count calculation and maximum check.
4. Zero-entry mode fallback.
5. `acl_cnt` assignment and phase initialization.
6. Permission validation before tag-phase validation.
7. Tag phase transitions and `UINT32_MAX` rules.
8. Duplicate named user and group ID checks.
9. ACL entry writes and undefined-ID mapping.
10. The short-circuit `phase != 6 || acl_posix1e_check(aclp) != 0` check.
No ACL zeroing or additional field initialization was added. Partial ACL
mutation on malformed input remains possible exactly as before.
## Branch Matrix
| Input or state | Preserved result |
| --- | --- |
| POSIX ACL mount option disabled | `EOPNOTSUPP` in caller |
| Unsupported ACL type | `EINVAL` in caller |
| Default ACL requested for non-directory | `EINVAL` in caller |
| No-ACL filter match | Mode-derived ACL in caller |
| ACL xattr absent | Mode-derived ACL in caller |
| Xattr lookup/read failure | Original error from caller |
| Oversized xattr or nonzero UIO residual | `EINTEGRITY` in caller |
| Short or misaligned wire value | `EINTEGRITY` in parser |
| Unsupported wire version | `EINTEGRITY` in parser |
| Entry count above limit | `EINTEGRITY` in parser |
| Zero entries | Mode-derived ACL in parser |
| Invalid permission, phase, tag, or ID | `EINTEGRITY` in parser |
| Duplicate named user/group ID | `EINTEGRITY` in parser |
| Missing required mask or incomplete phase | `EINTEGRITY` in parser |
| Valid ACL | Identical ACL entry population and success |
## Resource And Error Audit
- The input buffer remains stack-owned by `erofs_get_acl()`.
- The parser does not allocate, free, or retain pointers.
- There are no new cleanup paths.
- Xattr errors, UIO residual errors, wire errors, and ACL validation errors
retain their previous ordering.
- `erofs_xattr_load_body()` and its release paths were not modified.
- The optional header validation helper was intentionally omitted to avoid
touching the known header-only behavior.
## Static Verification
The batch is validated with:
- a strict two-file path whitelist;
- unique definitions of `erofs_get_acl()`, `erofs_acl_from_mode()`, and
`erofs_posix_acl_from_xattr()`;
- pre/post comparison of parser returns, phase transitions, ACL writes, and
duplicate-ID checks;
- `git diff --check`;
- confirmation that `erofs_xattr_load_body()` is absent from the source diff.
No build, QEMU smoke test, or feature test was run for this mechanical split.
+18
View File
@@ -0,0 +1,18 @@
# Pre11 Batch D
## Scope
- Rename `src/lz4.c` to `src/decompressor_lz4.c`.
- Update the source list and current architecture references to the new filename.
## Equivalence
- The pre-rename `lz4.c` blob and renamed `decompressor_lz4.c` blob are identical.
- Byte comparison confirms the renamed source content is unchanged.
- Rename detection reports a 100% rename.
- LZ4 definitions, declarations, descriptor references, and `lz4_finish` call counts are unchanged.
## Behavior and Testing
- This batch changes filenames and documentation only; it makes no behavior changes.
- No build, QEMU run, feature test, or other test was run.
+68
View File
@@ -0,0 +1,68 @@
# Pre11 Batch E: Mount Device Argument Cleanup
## Scope
This batch consolidates caller-owned `device.N` argument cleanup in
`erofs_mount()` and removes two impossible allocation checks. It does not
change mount option semantics, GEOM ownership, device slot validation, mount
error text, or any filesystem behavior.
Modified files:
- `src/super.c`
- `docs/pre11-batch-e.md`
## Changes
After `erofs_parse_device_options()` succeeds, every exit from
`erofs_mount()` now reaches one `out_args` label. Parse failure still returns
directly because no caller-owned argument array has been transferred.
The operation order remains:
1. `erofs_parse_device_options()`
2. `vfs_filteropt()`
3. `vfs_getopt()`
4. `erofs_open_device()`
5. `erofs_update_iosize_max()`
6. `erofs_mountfs()`
7. `erofs_free_device_args()`
8. `vfs_mountedfrom()`
9. `erofs_statfs()`
`out_args` only frees copied device arguments. It does not inspect or release
`primary`. The existing `erofs_mountfs()` ownership transfer and all GEOM
cleanup paths remain unchanged.
The checks for `NULL` immediately following allocation of `packed_inode` and
`metabox_en` were removed. Both allocations retain `M_WAITOK | M_ZERO`, so a
successful return cannot produce `NULL`. No other allocation checks or flags
were changed.
## Ownership Paths
| Path | Copied arguments | Primary device | Result |
| --- | --- | --- | --- |
| Parse failure | No caller ownership | Not opened | Return parser error directly |
| Option filter failure | Freed at `out_args` | Not opened | Return `EINVAL` |
| Invalid or missing `from` | Freed at `out_args` | Not opened | Return `EINVAL` |
| Primary open failure | Freed at `out_args` | Open helper retains its existing cleanup contract | Return open error |
| Mount setup failure | Freed at `out_args` | Ownership already transferred at `erofs_mountfs()` entry | Return mount error |
| Success | Freed at `out_args` | Owned by the mounted filesystem | Publish mounted-from and run `statfs()` |
## Static Verification
- `erofs_free_device_args()` has one caller after the change.
- `erofs_mountfs()` and `erofs_sb_free()` are unchanged.
- The relative order of option filtering, option access, primary open, I/O
sizing, mount setup, argument cleanup, mounted-from publication, and statfs
is unchanged.
- Existing errno values and mount error strings are unchanged.
- Duplicate and missing external-device slot behavior is unchanged.
- `git diff --check` passes.
- The diff is restricted to the two declared Batch E files.
## Validation Status
No build, QEMU smoke test, or feature test was run for this batch. Runtime
validation is deferred to the final Pre11 build and targeted smoke test.
+480
View File
@@ -0,0 +1,480 @@
# Pre11 Completion and Smoke Report
## Status
Pre11 is complete for its approved mechanical maintenance scope.
- Final implementation commit tested: `040fc69025c5031436205c3acd421be1d95fe86b`
- Final `repo-pre-11` tree: `f0cca92670c76ab13cc5b9353d5aeb993effa6e2`
- Final `repo-pre-11/src` tree: `c4aac4d69cb25a4b049239d0c1f422dcb89465d6`
- Static review: `ACCEPT`, with no P0-P3 findings in the included changes
- FreeBSD KLD build: `PASS`, using `WITH_ZSTDIO=0`
- Targeted plain/LZ4/LZMA QEMU smoke: `PASS`
- Full feature test and `test_all.py`: `NOT RUN`
This report does not claim full feature validation. Pre11 deliberately contains
mechanical naming, responsibility, dead-scaffolding, file-layout, and cleanup
changes that could be reviewed for behavioral equivalence and then covered by a
focused build and smoke run. Behavior changes requiring purpose-built fixtures
remain deferred.
## Snapshot and Planning
Pre11 was created from the completed Pre10 tree in:
```text
0483dcb41c2a980badb829ff4d17361bd1fec930
snapshot: create repo-pre-11 from repo-pre-10
```
At the snapshot boundary, `repo-pre-10/src` and `repo-pre-11/src` were both:
```text
ec684d8dbd7662070234da1fe947aba48a293db8
```
The executable Pre11 plan was added in:
```text
9ad1fa0cf8ba251b574da033bf0c9fad83acaa38
docs: add pre11 execution plan
```
The original plan included several candidates that would have changed visible
filesystem behavior. An independent boundary review narrowed the release in:
```text
c89c228ac1817cfd0337905b39247245eaa67f3b
docs: narrow pre11 execution boundaries
```
The narrowed scope retained only work that could preserve control flow, errno,
on-disk interpretation, vnode and GEOM ownership, iterator order, decompressor
semantics, and cache lifetime. The following candidates were removed from
implementation because honest validation would require targeted feature tests:
- root vnode type rejection;
- zero-length mapping behavior in the UIO reader;
- header-only xattr semantics;
- duplicate or missing `device.N` behavior;
- direct propagation of `vnode_create_vobject()` errors;
- broad map, inode, directory, and name-lookup interface reshaping.
This narrowing allowed Pre11 to remain a comparatively large maintenance
release without mixing low-risk structural alignment with unvalidated semantic
changes.
## Batch A: Type and Vocabulary Alignment
Commit:
```text
b4e38c9fb15df127470d70bc144404f1f3afa2af
pre11: align mount and device helper vocabulary
```
Exact changes:
- renamed `struct erofs_mount.block_bits` to `blkszbits` across its consumers;
- renamed the private `erofs_fill_map_dev()` helper to
`erofs_fill_from_devinfo()`;
- added the Batch A implementation report.
Linux-maintenance benefit:
- `blkszbits` matches established EROFS vocabulary and makes comparisons with
Linux mount geometry code more direct;
- `erofs_fill_from_devinfo()` describes the same responsibility as the Linux
helper without importing Linux block-device objects into FreeBSD.
BSD invariants retained:
- field type, width, units, expressions, overflow handling, and structure
layout were unchanged;
- the helper remained a private FreeBSD device-map field filler;
- GEOM provider selection, range checks, flat-device behavior, errno, and
physical I/O remained unchanged;
- no broad or search-driven integer type replacement was performed.
Static checks confirmed 48 intended `block_bits` replacements, zero remaining
old identifiers, four helper-name replacements, zero stale helper references,
and a source diff containing only the approved identifier changes.
## Batch B: Vnode Operation Scaffolding
Commit:
```text
67625d3afebb9142e69b19afd08cdfc1d2adaac5
refactor: clean up vnode operation scaffolding
```
Exact changes:
- removed the unused `EROFS_MOUNT_XATTR_USER` option bit;
- removed the zero-consumer `erofs_node.vnode` member and its only assignment;
- grouped VOP declarations and `erofs_vnodeops` initializers by responsibility;
- added the Batch B implementation report.
Linux-maintenance benefit:
- removes misleading compatibility-shaped state that had no consumer;
- presents vnode operations in a responsibility-oriented order that is easier
to compare with upstream filesystem responsibilities while retaining the
native FreeBSD operation table.
BSD invariants retained:
- no VOP function definition or registration changed;
- `erofs_vnodeops` retained 24 unique `field=value` entries;
- `erofs_fifoops` retained 14 unique entries and its original sequence;
- `fifo_specops`, vnode locking, hash insertion, reclaim, pager behavior, and
`vnode_create_vobject()` errno translation were unchanged.
The removed symbols had no reads, address consumers, initializer dependencies,
offset consumers, or external ABI use.
## Batch C: ACL Parsing Responsibilities
Commit:
```text
a86b5cac4d193d131de8870bca4132fdc881f992
erofs: split ACL parsing responsibilities
```
Exact changes:
- extracted private `erofs_posix_acl_from_xattr()` from `erofs_get_acl()`;
- changed the private mode fallback helper to receive the inode mode directly;
- left xattr acquisition and all FreeBSD VOP/UIO responsibilities in the
caller;
- added the Batch C implementation report.
Linux-maintenance benefit:
- separates wire-format ACL parsing from filesystem acquisition and VOP glue,
matching the upstream responsibility split more closely;
- gives maintainers a bounded parser to compare without introducing Linux ACL
objects, RCU, xattr handlers, or inode lifecycle assumptions.
BSD invariants retained:
- mount-option checks, ACL type validation, default-ACL directory checks,
namespace selection, both `erofs_getxattr()` stages, UIO setup, and `ENOATTR`
fallback remain in `erofs_get_acl()`;
- parser validation order, positive errno, tag phases, duplicate ID checks,
`UINT32_MAX` handling, partial ACL writes, and final
`acl_posix1e_check()` ordering remain unchanged;
- input storage remains caller-owned stack memory, with no new allocation or
cleanup path;
- `erofs_xattr_load_body()` and the known header-only xattr behavior were not
changed.
Static comparison confirmed equivalent branch, error, phase-transition, and
ACL-population behavior.
## Batch D: LZ4 Backend File Responsibility
Commit:
```text
e631e33a1b8c053ebcb2d737af33e363cba48b34
erofs: align LZ4 backend file responsibility
```
Exact changes:
- renamed `src/lz4.c` to `src/decompressor_lz4.c`;
- updated the Makefile source entry;
- updated current architecture documentation references;
- added the Batch D implementation report.
Linux-maintenance benefit:
- makes the filename communicate that the translation unit is the LZ4 codec
backend beside `decompressor_lzma.c`, `decompressor_deflate.c`, and
`decompressor_zstd.c`;
- reduces unnecessary file-layout differences when maintainers inspect codec
implementations across the independent Linux and FreeBSD repositories.
BSD invariants retained:
- Git identified a 100% rename;
- the old and new source blobs are both
`2cc19f5eb0c283e12ba0a3a4334b8817a1686888`;
- function bodies, request ABI, descriptor registration, input/output bounds,
partial decode behavior, and error normalization were unchanged;
- historical reports and historical test evidence were not rewritten.
## Batch E: Mount Device Argument Cleanup
Commit:
```text
040fc69025c5031436205c3acd421be1d95fe86b
erofs: consolidate mount device argument cleanup
```
Exact changes:
- consolidated caller-owned copied `device.N` argument release into one
`out_args` exit in `erofs_mount()`;
- removed two impossible NULL checks immediately following
`M_WAITOK | M_ZERO` allocations;
- added the Batch E implementation report.
Linux-maintenance benefit:
- makes mount setup ownership and cleanup easier to audit by reducing repeated
release sites;
- removes defensive branches that contradict the FreeBSD `M_WAITOK` contract,
keeping BSD-specific allocation behavior explicit instead of emulating a
nullable Linux allocation path.
BSD invariants retained:
- operation order remains parse, filter, get option, open primary, update I/O
size, mount setup, free copied arguments, publish mounted-from, and statfs;
- parse failure still returns before caller ownership exists;
- `out_args` releases only copied arguments and never releases `primary`;
- `erofs_mountfs()` still takes ownership of `primary` at entry;
- `erofs_mountfs()`, `erofs_sb_free()`, reverse external-device cleanup, errno,
and mount error text are unchanged;
- duplicate and missing external-device slot behavior remains deferred.
## Static Review
Independent reviews were performed after the individual batches and again over
the complete source range from `0483dcb` through `040fc690`.
The final review found no P0-P3 issue in the included Pre11 source changes and
accepted all five batches. It confirmed:
- the source changes were confined to the approved Batch A-E paths;
- no deferred behavior fix entered the source;
- old identifiers had no stale consumers and new private helpers had unique
definitions and expected call counts;
- both VOP vectors retained the same registration mappings;
- ACL acquisition, parsing, fallback, errno, and partial-write behavior were
preserved;
- the LZ4 backend was a byte-identical rename and the Makefile contained the
new filename once;
- mount cleanup retained error order and GEOM ownership;
- uncompressed mapping interfaces, directory/name lookup, decompressor request
ABI, LZMA cache policy, disk structures, and Linux reference sources were not
changed;
- `git diff --check` passed and the implementation worktree was clean before
runtime validation.
The final source delta from the snapshot contains 202 insertions and 188
deletions across ten source paths, including the 100% LZ4 file rename. Much of
that textual delta is identifier replacement, declaration/vector reordering,
or moved ACL parser code rather than new behavior.
## First Smoke Attempt: Infrastructure Failure
Evidence directory:
```text
/work/tests-dev/temp/pre11-smoke-final-20260813T122923Z
```
Result: `FAIL`, specifically an infrastructure failure before DUT transfer or
execution.
The guest reached FreeBSD and two SSH identity probes succeeded, but the first
runner opened an SSH ControlMaster using a detached invocation. The open command
returned success and created the control socket, then the master terminated or
lost its connection before the immediate health check. The check failed with:
```text
Control socket connect(.../control.sock): Connection refused
```
The test stopped before source transfer, KLD build, module load, or any EROFS
mount. Therefore this attempt is not a PASS, but it is also not evidence of a
Pre11 build or driver failure. Its source archive SHA was:
```text
5bbffb101364f350fe1a3771b6f018bc10a68785d8c55cc2a186273a074e9182
```
The owned QEMU stopped, port `10043` was released, its overlay was removed, the
base image remained unchanged, and the retained guard VM remained reachable.
The run's cleanup status was nonzero only because closing the already-dead
ControlMaster returned an error.
## Corrected Smoke Retry
Evidence directory:
```text
/work/tests-dev/temp/pre11-smoke-retry-final-20260813T124214Z
```
Result: `PASS`.
The retry used an SSH master process in normal foreground mode but launched it
in the runner shell background, without `ssh -f`. The runner captured the real
master PID, repeatedly required both `kill -0` and `ssh -O check` to succeed,
and refused to transfer sources unless the master remained alive. Long build
work ran inside the guest with a completion sentinel, so one long SSH command
was not treated as the build lifetime.
Tested identities:
```text
commit: 040fc69025c5031436205c3acd421be1d95fe86b
repo-pre-11 tree: f0cca92670c76ab13cc5b9353d5aeb993effa6e2
src tree: c4aac4d69cb25a4b049239d0c1f422dcb89465d6
source archive: 179b80365c26295770afab58f1209ae3fffd73182ada0bcf97ea027904f781e9
guest: FreeBSD 15.0-RELEASE-p8
```
The archive hashes differ between attempts because separately generated gzip
archives are not required to be byte-reproducible. Both identity files name the
same committed repository and source trees; only the passing retry is used as
runtime evidence.
## Build and Module Lifecycle
The committed `repo-pre-11` archive was transferred to the disposable guest,
validated there, extracted, and built with:
```sh
make WITH_ZSTDIO=0
```
Results:
- guest build return code: `0`;
- `erofs.ko` SHA256:
`77ce41bff5cb7d7c934dd9dba20ba06c20b660ad15ebfc4210f86f6473dffbf7`;
- `kldload /root/pre11-smoke/erofs.ko`: success;
- `kldstat -n erofs`: success, reporting `erofs.ko` loaded;
- all smoke mounts and md providers were released;
- `kldunload erofs`: success;
- the post-unload `kldstat -n erofs` check confirmed the module was absent.
`WITH_ZSTDIO=1` was not built and is not claimed as tested.
## Smoke Cases
Each case used an independently attached vnode-backed md provider, required a
successful EROFS mount to be visible in `mount -p`, computed the complete target
file hash, then unmounted and detached the provider before the next case.
| Case | Target SHA256 | Elapsed | Provider | Result |
| --- | --- | ---: | --- | --- |
| Plain | `056f8f7585667dc695e2edf936deaf84cfee0f88671ba6cd5be22ce890763433` | 0 s | `md70` | PASS |
| LZ4 | `3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880` | 5 s | `md71` | PASS |
| LZMA | `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461` | 2 s | `md72` | PASS |
The LZMA case hashed the complete file. No timeout, mount failure, hash mismatch,
unmount failure, or md detach failure occurred.
## Kernel Messages and Cleanup
The runner captured `dmesg` before loading the module and after all smoke cases.
The delta contained no EROFS error, decompression failure, panic, or assertion;
the suspect-output file is empty.
Cleanup evidence records:
```text
qemu_stopped=1
port_released=1
overlay_removed=1
base_mode_after=444
base_sha_after=67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef
guard_unchanged_and_reachable=1
body_rc=0
cleanup_rc=0
```
The test owned only port `10044`, its QEMU PID, its ControlMaster PID, and its
overlay. All were closed or removed. The permanent base image was never booted
directly and retained mode `0444` and its original SHA256. The retained guard VM
PID `26318` kept start time `1102996924`, an identical command line, and a
reachable forwarded SSH port at `9222`.
## Feature-Test Decision
No feature test and no `test_all.py` run was performed.
That decision is appropriate for the included Pre11 scope because:
- Batch A changed only private identifiers;
- Batch B removed proven dead state and reordered designated VOP initializers
without changing their mappings;
- Batch C mechanically moved parser statements while retaining inputs, outputs,
writes, error order, and ownership;
- Batch D was a byte-identical file rename;
- Batch E consolidated one caller-owned cleanup action and removed checks that
cannot be reached after `M_WAITOK` allocation.
Static equivalence review plus the FreeBSD build and plain/LZ4/LZMA smoke is
sufficient evidence for these changes. It is not sufficient for the deferred
behavior changes below, and this report does not claim otherwise.
## Deferred Behavior and Required Targeted Tests
### Non-directory root
The current root path does not add a new rejection for a root NID that resolves
to a non-directory vnode. A future fix requires a purpose-built image and checks
for mount errno, vnode release, mount error text, and complete cleanup.
### Zero-length internal run
The uncompressed UIO reader's existing `run_len == 0` behavior was not changed.
A future fix requires a corrupt mapping fixture that reaches a zero-length run
while the request remains inside the file, with exact comparison to the
contiguous-buffer path and expected `EINTEGRITY`.
### Header-only xattr
The existing behavior for an ibody containing only the xattr header was not
changed. Targeted coverage must distinguish zero and nonzero shared counts,
name filters, get/list results, ACL mode fallback, and corruption results.
### Duplicate or missing device slots
`device.N` slot parsing behavior and diagnostics were not changed. Future tests
must cover duplicate, out-of-order, missing, excessive, and malformed slots,
flat and non-flat device tables, exact errno and mount error text, and GEOM
cleanup.
### `insmntque()` race closure
The earlier ownership fix remains accepted by static lifecycle review but is
not runtime-closed by ordinary smoke. A dedicated mount/unmount or forced
unmount race must stress vnode creation and inspect panic, use-after-free, lock,
reference, and cleanup behavior.
### Explicit mapped extent fixtures
Positive payload fixtures are still unavailable for explicit mapped extent
records of 4, 8, 16, and 32 bytes. No restructuring or coverage claim should be
made until validated mappings and file hashes exist.
### `vnode_create_vobject()` errno
Pre11 intentionally retains the current conversion of a nonzero
`vnode_create_vobject()` result to `ENOMEM`. Direct propagation is a visible
behavior change and requires controlled pager failure or fault injection that
verifies the caller-visible errno and vnode state.
## Final Conclusion
Pre11 completed all five narrowed maintenance batches, passed independent
static review, built successfully as a FreeBSD KLD, and passed the required
plain, LZ4, and full-file LZMA smoke. The first smoke attempt was correctly
classified as an infrastructure failure before DUT transfer or build; the
corrected retry provides the runtime evidence.
Pre11 improves cross-repository maintainability without replacing native
FreeBSD vnode, GEOM, UIO, pager, ACL, or synchronous decompression contracts.
Full feature validation remains outside this release and must not be inferred
from the targeted smoke result.
+11
View File
@@ -0,0 +1,11 @@
# Pre12 Baseline
`repo-pre-12` was created as an exact snapshot copy of `repo-pre-11` before this document was added.
- Source commit: `88fc67742e6c173e14538d4fcd556a45a385c2c9`
- Source tree (`repo-pre-11`): `5b8c637bcb535f330d894f30660bb5e65db70048`
- Copied tree before this document (`repo-pre-12`): `5b8c637bcb535f330d894f30660bb5e65db70048`
- Source `src` tree: `c4aac4d69cb25a4b049239d0c1f422dcb89465d6`
- Copied `src` tree: `c4aac4d69cb25a4b049239d0c1f422dcb89465d6`
Copy verification used a recursive comparison that preserved and checked file modes and symbolic links. The complete tree IDs matched before this document was added. Adding this document does not modify the copied `src` tree.
+29
View File
@@ -0,0 +1,29 @@
# Pre12 Batch 01
This batch aligns the planned in-memory NID, block-number, and byte-offset
declarations with the existing `erofs_nid_t`, `erofs_blk_t`, and
`erofs_off_t` aliases.
The source whitelist was limited to eight fields in `internal.h`, the
`erofs_iloc`, `erofs_nid_is_valid`, `erofs_read_inode`, and `erofs_vfs_hash`
declarations and definitions, and the byte-offset parameters of
`erofs_bread`, `erofs_read_physical`, and `erofs_read_metadata`.
All three aliases are direct `uint64_t` typedefs. Static assertions confirmed
that each is an unsigned, 8-byte type with the same alignment and exact C type
compatibility as `uint64_t`. Consequently every replaced field has identical
size and alignment, so both structure sizes, alignments, field order, and all
target `offsetof` values are unchanged. Function parameter and return ABI
representations are also unchanged, and the vnode hash still consumes the
same complete 8-byte NID object.
The source diff is limited to the planned fields, function declarations and
definitions, and necessary local declaration splits. No expressions, field
order, on-disk types, control flow, error handling, or formatting outside
those declarations changed.
Validation used exact declaration inventory checks, token-diff inspection,
declaration/definition signature comparison, arithmetic and hash review,
compile-time type assertions, and `git diff --check`. No planned point was
skipped and no out-of-scope candidate was changed. No build, QEMU, smoke,
feature, or other dynamic test was run.
+31
View File
@@ -0,0 +1,31 @@
# Pre12 Batch 02
This batch replaces exactly three potentially unaligned typed loads with the
FreeBSD little-endian byte decoders planned for Pre12.
- `xattr.c`: `hdrbuf` contains two raw metadata bytes returned by
`erofs_xattr_read_backing(..., sizeof(raw_len), ...)`; `le16dec(hdrbuf)`
decodes the prefix-record length.
- `inode.c`: `buf` contains at least one complete 32-byte compact on-disk inode
returned by `erofs_read_metadata`; its first two bytes are the little-endian
`i_format` field decoded by `le16dec(buf)`.
- `data.c`: the non-indexes branch sets `entry_size` to
`EROFS_BLOCK_MAP_ENTRY_SIZE == sizeof(__le32) == 4`; `idx` points to those
four raw block-map bytes and `le32dec(idx)` decodes the block address.
For bytes `b0..b3`, both old expressions and the new helpers produce
`b0 | b1 << 8` or `b0 | b1 << 8 | b2 << 16 | b3 << 24`. On a little-endian
host the typed load already has that value; on a big-endian host `leXXtoh`
byte-swaps the typed value to that same formula. The helpers preserve this
result while avoiding any alignment requirement.
`xattr.c` and `data.c` receive the helpers through `internal.h` to
`erofs_fs.h` to `<sys/endian.h>`; `inode.c` also includes `<sys/endian.h>`
directly. Existing `le16dec` and `le32dec` consumers confirm the established
include convention. Error checks, branches, value ranges, and buffer release
ordering are unchanged. No point was skipped and no other endian read was
changed.
Validation used exact-expression counts, buffer-source and length inspection,
symbolic endian equivalence, source-diff inspection, and `git diff --check`.
No build, QEMU, smoke, feature, or other dynamic test was run.
+49
View File
@@ -0,0 +1,49 @@
# Pre12 Batch 03: POSIX ACL mode helper
## Change
- In `src/xattr.c`, `erofs_acl_from_mode` now obtains the owner, group, and
other permissions with `acl_posix1e_mode_to_perm`.
- The helper tags are `ACL_USER_OBJ`, `ACL_GROUP_OBJ`, and `ACL_OTHER`, matching
the adjacent `ae_tag` assignments.
- No other function lines, ACL entry ordering, `acl_cnt`, IDs, default ACL
handling, fallback behavior, flags, errno behavior, or control flow changed.
## Helper confirmation
- The local FreeBSD header `sys/sys/acl.h` declares
`acl_posix1e_mode_to_perm(acl_tag_t tag, mode_t mode)` for kernel consumers.
- The local FreeBSD implementation in `sys/kern/subr_acl_posix1e.c` maps
`ACL_USER_OBJ` from `S_IRWXU`, `ACL_GROUP_OBJ` from `S_IRWXG`, and
`ACL_OTHER` from `S_IRWXO`, returning the corresponding POSIX ACL read,
write, and execute permission bits.
- `src/xattr.c` already includes `<sys/acl.h>`; no include change was needed.
## Exhaustive equivalence
A temporary standalone C program reproduced the local FreeBSD helper logic and
compared it with the three replaced expressions for every permission mode from
`0000` through `0777`:
```text
equivalent=1536/1536 modes=512 tags=3 mismatches=0
temporary_cleanup=ok
```
This covers 512 modes for each of the three tags. The temporary source and
executable were deleted and are not part of the repository.
## Static validation
- The `erofs_acl_from_mode` function is line-for-line unchanged except for the
three `ae_perm` expressions.
- The source diff changes only those three expressions; this document is the
only added file.
- `git diff --check` and `git diff --cached --check` completed without errors.
- Before commit, both unstaged status and staged status contained only
`src/xattr.c` and `docs/pre12-batch-03.md` under `repo-pre-12`.
## Tests not run
Per the Batch 03 execution constraints, no project build, QEMU run, smoke test,
or feature test was run.
+35
View File
@@ -0,0 +1,35 @@
# Pre12 Batch 04: Private compression header
## Change
- Added `src/compress.h` for the decompressor request, descriptor, and backend
private declarations previously located in `src/internal.h`.
- Added the header only to `decompressor.c` and the LZ4, LZMA, Deflate, and
ZSTD backend translation units.
- Kept the runtime compression enum, `z_erofs_parse_cfgs`, and the public
`z_erofs_decompress` declaration in `src/internal.h`.
- Preserved every migrated declaration signature and storage duration. No
function body, callback, conditional compilation block, errno path, or
buffer ownership changed.
## Static validation
- The `compress.h` consumer set is exactly the five planned decompressor
translation units; `super.c`, `zdata.c`, and all other public callers still
include only `internal.h`.
- The include graph is acyclic: `compress.h` includes `internal.h`, while
`internal.h` does not include `compress.h`.
- Exact searches found one definition of each migrated structure and one
declaration of each migrated backend function. The old declarations are
absent from `internal.h`.
- The new header contains no Linux page, folio, bio, workqueue, XArray,
shrinker, compatibility, or related runtime declarations.
- Source diff inspection and declaration normalization confirmed that this is
a declaration-only move plus the five include additions.
- `git diff --check` and `git diff --cached --check` completed without errors.
## Tests not run
Per the execution constraints, no build, QEMU run, smoke test, feature test,
or other dynamic test was run. The planned `WITH_ZSTDIO=0` and
`WITH_ZSTDIO=1` KLD builds remain required during final validation.
+31
View File
@@ -0,0 +1,31 @@
# Pre12 Batch 05: ZSTD window lower bound
## Change
- Removed only the `rq->em->zstd_windowlog + 10 < 10` subcondition from
`z_erofs_zstd_decompress`.
- Preserved the `> 20` upper bound, the `outputsize == 0` check, every
`return (-1)`, all ZSTD calls, resource release, and the complete disabled
`#else` stub.
- Did not change ZSTD configuration parsing, accepted configuration range,
conditional compilation, errno behavior, or control flow after the guard.
## Static proof
- `struct erofs_mount.zstd_windowlog` is `uint8_t`, so integer promotion gives
a value in the range 0 through 255.
- Adding 10 therefore produces a minimum value of 10; comparison with `< 10`
is unreachable for every representable field value.
- `z_erofs_load_zstd_config` still rejects `windowlog > 10`, so configured
mount values and the remaining decompression upper-bound behavior are
unchanged.
- Normalized function comparison confirms that the only removed logical
tokens are the single unreachable lower-bound subcondition and its `||`.
- `git diff --check` and `git diff --cached --check` completed without errors.
## Tests not run
Per the execution constraints, no build, QEMU run, smoke test, feature test,
or other dynamic test was run. A FreeBSD KLD build with `WITH_ZSTDIO=1`
remains mandatory during final validation so this enabled function body is
compiled.
+60
View File
@@ -0,0 +1,60 @@
# Pre-12 Batch 06: codec descriptor ownership
Date: 2026-08-13
## Scope
- Moved the LZMA, DEFLATE, and ZSTD descriptors into their backend source
files.
- Made their backend-only config, decompress, and availability helpers
`static`.
- Kept the LZ4 config loader and descriptor, plus the SHIFTED and INTERLACED
descriptors and transform, in `src/decompressor.c`.
- Replaced the central descriptor object array with the descriptor pointer
table `z_erofs_decomp`.
- Reduced `src/compress.h` to the three backend descriptor declarations and
the LZ4 decompress prototype needed by the central descriptor.
## Descriptor tuples
The normalized tuples are unchanged before and after this batch:
| Index | Name | Config callback | Decompress callback | Compile condition |
| --- | --- | --- | --- | --- |
| `Z_EROFS_COMPRESSION_LZ4` | `lz4` | `z_erofs_load_lz4_config` | `z_erofs_lz4_decompress` | always |
| `Z_EROFS_COMPRESSION_LZMA` | `lzma` | `z_erofs_load_lzma_config` | `z_erofs_lzma_decompress` | always |
| `Z_EROFS_COMPRESSION_DEFLATE` | `deflate` | `z_erofs_load_deflate_config` | `z_erofs_deflate_decompress` | always |
| `Z_EROFS_COMPRESSION_ZSTD` | `zstd` | `z_erofs_load_zstd_config` | `z_erofs_zstd_decompress` | descriptor and config always; decompress implementation selected by `ZSTDIO` |
| `Z_EROFS_COMPRESSION_SHIFTED` | `shifted` | `NULL` | `z_erofs_transform_plain` | always |
| `Z_EROFS_COMPRESSION_INTERLACED` | `interlaced` | `NULL` | `z_erofs_transform_plain` | always |
## Static validation
- Confirmed one declaration and one definition for each backend descriptor;
the only additional occurrence is its central pointer-table entry.
- Confirmed the LZMA and DEFLATE config/decompress helpers have no external
consumers and are now `static` in their backends.
- Confirmed the ZSTD config/decompress/availability helpers have no external
consumers and are now `static`; the two decompress definitions are mutually
exclusive under `#ifdef ZSTDIO`.
- Confirmed `z_erofs_decomp` explicitly fills all six runtime indices in the
original order. Decompression retains the array-length bound, rejects a
null table entry, and rejects a null decompress callback with
`EOPNOTSUPP`. Config parsing retains the existing compression-config loop
bound and treats a null entry or null config callback as `EOPNOTSUPP` before
releasing the config buffer on the existing path.
- Confirmed the `WITH_ZSTDIO=0` static conditional path remained valid;
actual build deferred to final validation. Without `ZSTDIO`, the path
keeps the descriptor non-null, reports the existing mount error and
`EOPNOTSUPP` from config, and selects the decompress stub returning `-1`.
- Confirmed `WITH_ZSTDIO=1` adds `-DZSTDIO`, keeps the same descriptor and
config callback, and selects the existing ZSTD implementation with its
resource-release and error paths unchanged.
- `git diff --check` and `git diff --cached --check` were required for this
batch.
## Deferred validation
No build, QEMU, smoke, feature, or other dynamic validation was run for this
batch. Follow-up validation must build the FreeBSD KLD with both
`WITH_ZSTDIO=0` and `WITH_ZSTDIO=1`.
+39
View File
@@ -0,0 +1,39 @@
# Pre-12 Batch 07: exact macro alignment
Date: 2026-08-13
## Scope
- Renamed only `EROFS_ALL_SUPPORTED_INCOMPAT` to the Linux name
`EROFS_ALL_FEATURE_INCOMPAT` in its comment, definition, and sole source
use.
- Deleted only the zero-consumer compatibility alias
`EROFS_CHUNK_FORMAT_INDEXES_FLAG`.
## Static proof
- Before the edit, the target tree had exactly three source occurrences of
`EROFS_ALL_SUPPORTED_INCOMPAT`: its comment and definition in
`src/erofs_fs.h`, and the `src/super.c` unsupported-feature calculation.
The two additional target-tree matches were historical review prose, not
source or build consumers.
- After the edit, the old source name has zero occurrences and
`EROFS_ALL_FEATURE_INCOMPAT` has exactly the same comment, definition, and
sole `src/super.c` use.
- The macro continuation expression and its feature-bit operands are
unchanged. The unsupported calculation changes only the macro token.
- A tracked-tree exact-symbol search found
`EROFS_CHUNK_FORMAT_INDEXES_FLAG` only as identical definitions in retained
repository snapshots. The target tree had exactly one occurrence: the
definition being deleted.
- Exact searches of target-tree Makefiles, scripts, documentation, text
interfaces, and preprocessor `-D` flags found no consumer of the deleted
alias. Existing code continues to use `EROFS_CHUNK_FORMAT_INDEXES`;
`EROFS_CHUNK_FORMAT_ALL` and chunk decoding are unchanged.
- `git diff --check` and `git diff --cached --check` were required for this
batch.
## Deferred validation
No build, QEMU, smoke, feature, or other dynamic validation was run for this
batch. The final KLD and Plain/LZ4/LZMA validation remains deferred.
+32
View File
@@ -0,0 +1,32 @@
# Pre-12 Batch 08: xattr helper order
Date: 2026-08-13
## Scope
- Moved the complete `static erofs_listxattr_foreach` function block to
immediately precede `erofs_getxattr_foreach` in `src/xattr.c`.
- Made no changes to either function body, signature, visibility, callers, or
conditional-compilation context.
## Static proof
- The exact function block from `static int` through its closing brace had
SHA-256 `0541c211f78a19f39caeebdafe73bdd0a7064e996686a7b45c9c651160ed98ae`
before and after the move.
- The target remains one `static` definition with two call sites, in the
inline and shared xattr iterators.
- `erofs_getxattr_foreach` remains one definition with the same two call
sites; its complete block is unchanged.
- Both helpers remain in the same unconditional compilation region after
`struct erofs_xattr_iter` and before the iterator functions, so no forward
declaration or conditional boundary was introduced.
- The source diff is a pure block move; adjacent xattr and ACL logic is
unchanged.
- `git diff --check` and `git diff --cached --check` were required for this
batch.
## Deferred validation
No build, QEMU, smoke, feature, or other dynamic validation was run for this
batch. Final KLD and Plain/LZ4/LZMA validation remains deferred.
+44
View File
@@ -0,0 +1,44 @@
# Pre-12 Batch 09: compression header responsibility
Date: 2026-08-13
## Scope
- Removed the direct `internal.h` include from `decompressor.c` and the four
`decompressor_*.c` backend consumers that already include `compress.h`.
- Made no declaration, definition, function, storage-duration, or public API
changes.
## Candidate source
Batch 04 created `compress.h` with a direct `internal.h` include and added
`compress.h` to these five translation units while retaining their direct
`internal.h` includes. Batch 06 then made LZMA, DEFLATE, and ZSTD backend
functions private and exposed only their descriptor objects through
`compress.h`. This left exactly five duplicate include edges attributable to
Batches 04/06.
## Static proof
- `compress.h` remains the sole owner of `z_erofs_decompress_req`,
`z_erofs_decompressor`, the LZ4 decompressor prototype, and the three
backend descriptor declarations.
- `internal.h` remains the sole public owner of `z_erofs_decompress` and
`z_erofs_parse_cfgs`; it does not include `compress.h`.
- The include graph remains acyclic: each affected translation unit includes
`compress.h`, and `compress.h` includes `internal.h`, which includes only
`erofs_fs.h` among project headers.
- Each affected consumer therefore still sees the same complete request,
descriptor, mount, map, and superblock types through one guarded include
path. No backend prototype or descriptor declaration is duplicated between
`internal.h` and `compress.h`.
- The source diff removes exactly one `#include "internal.h"` line from each
of five authorized files and changes no other token.
- `git diff --check` and `git diff --cached --check` were required for this
batch.
## Deferred validation
No build, QEMU, smoke, feature, or other dynamic validation was run under the
execution constraint. The required FreeBSD KLD `WITH_ZSTDIO=0/1` matrix and
final Plain/LZ4/LZMA validation remain deferred.
+24
View File
@@ -0,0 +1,24 @@
# Pre-12 Batch 10: Makefile readability review
Date: 2026-08-13
## Result
No-op. `src/Makefile` was reviewed after Batches 04, 06, and 09, but no
non-redundant comment or blank-line grouping with clear maintenance value was
identified.
## Static proof
- The `SRCS` list already keeps the core filesystem sources, mapping and data
sources, and `decompressor_*.c` sources in readable contiguous regions.
- Adding labels would repeat names already visible in the short source list.
- `src/Makefile` is byte-for-byte unchanged.
- The `SRCS` elements, order, duplicate counts, `WITH_ZSTDIO` condition,
per-file CFLAGS, target, and `.include` line are unchanged.
- `git diff --check` and `git diff --cached --check` were required for this
documentation-only closeout.
## Validation
No build, QEMU, smoke, or feature test was run for this no-op review.
+229
View File
@@ -0,0 +1,229 @@
# Pre12 Final Report
Date: 2026-08-13
## Conclusion and scope
Pre12 is complete for its deliberately limited scope. The final result is
**PASS** for the planned static review, both FreeBSD KLD build configurations,
KLD load/status/unload, and the ordinary Plain/LZ4/LZMA QEMU smoke cases.
This PASS is not a claim of complete EROFS feature validation. No feature
test, `test_all.py`, new fixture, ZSTD data test, DEFLATE data test, ACL runtime
test, explicit-extent test, or multi-device test was run. The included changes
were selected as low-side-effect maintenance work and did not require a full
feature suite after their static proofs and final build/smoke matrix. Deferred
behavior work remains unresolved and is listed below.
The validated device-under-test identity is:
| Identity | Value |
| --- | --- |
| Commit | `a55e42a117e504f6ece49be3c2a2d81603031ea3` |
| `repo-pre-12` tree | `9161671bd0e855faa52c37e24fe8efcd6ddd71e0` |
| `repo-pre-12/src` tree | `d63c45c7872dcf9dd84007c8883cf9fbe0f9467d` |
| Source archive SHA-256 | `bb7e7f0383be29102475575e130e0b19a48452d20520c94059dc6ef128d0c7ce` |
## Goals
Pre12 continued the staged effort to reduce unnecessary maintenance
differences from Linux EROFS without importing Linux-only memory, I/O, device,
or concurrency models into FreeBSD. Its concrete scope was nine core batches
and one conditional maintenance closeout:
1. Use existing semantic scalar aliases where their width and signedness are
exactly equivalent.
2. Use FreeBSD unaligned little-endian readers for raw on-disk bytes.
3. Use the FreeBSD POSIX.1e ACL mode conversion facility.
4. Introduce a Linux-comparable private compression header.
5. Remove an unreachable ZSTD lower-bound condition.
6. Align codec descriptor ownership while preserving FreeBSD behavior.
7. Align exact macro vocabulary and remove one zero-consumer alias.
8. Align one xattr helper's file order through a byte-identical block move.
9. Remove include duplication introduced by the compression-header work.
10. Review Makefile readability and make no change when no useful grouping
was found.
The work explicitly excluded broad file reordering, catch-all style cleanup,
Linux page/folio/bio/workqueue infrastructure, and behavior changes requiring
specialized feature fixtures.
## Batch results
| Batch | Commit | Result and summary |
| --- | --- | --- |
| 01 | `bb28c47bdf4de777febde05ebb333c420ea1e9b9` | Replaced the planned NID, block-number, and byte-offset declarations with existing equal-width unsigned `erofs_nid_t`, `erofs_blk_t`, and `erofs_off_t` aliases. Field order, layout, ABI width, expressions, on-disk types, and control flow were unchanged. |
| 02 | `76a655788538f2270626cff1ca91b8c47700d536` | Replaced exactly three potentially unaligned typed loads with `le16dec`/`le32dec` in xattr, inode, and block-map decoding. |
| 03 | `35bde15f5242f84a6343cc9e96519fcce4eeca90` | Replaced three manual ACL mode shifts with `acl_posix1e_mode_to_perm` for owner, group, and other entries. |
| 04 | `b591751f90e62bc0ec54d3f34c724f698c99813d` | Added `src/compress.h` for private decompressor request, descriptor, and backend declarations; public entry points and runtime enum remained in `internal.h`. |
| 05 | `aaa3c5db40745ee558e6fcf6375d60e044217df9` | Removed only the unreachable `zstd_windowlog + 10 < 10` subcondition; the upper bound, zero-output check, disabled stub, errno behavior, and resource handling remained unchanged. |
| 06 | `6748d58f7e15927321f44a83d81a6c697c1185ae` | Moved LZMA, DEFLATE, and ZSTD descriptors into their backends, kept LZ4 and plain transforms central, and changed the central descriptor table to the pointer table `z_erofs_decomp`. All six normalized descriptor tuples and indices remained unchanged. |
| 07 | `292da21bd9288cac4a8b280bb67970c8b9058550` | Renamed `EROFS_ALL_SUPPORTED_INCOMPAT` to Linux's `EROFS_ALL_FEATURE_INCOMPAT` at its definition and sole source use, and removed zero-consumer `EROFS_CHUNK_FORMAT_INDEXES_FLAG`. |
| 08 | `0be5642d917104f1904bcfadaea803c9845c7a5c` | Moved the complete `erofs_listxattr_foreach` static function block before `erofs_getxattr_foreach`; the block SHA-256 remained `0541c211f78a19f39caeebdafe73bdd0a7064e996686a7b45c9c651160ed98ae`. |
| 09 | `e5315489eb5ed08884d5d5f2645264067e36838e` | Removed five direct `internal.h` includes made redundant by `compress.h`; declaration visibility and the acyclic include graph were preserved. |
| 10 | `a55e42a117e504f6ece49be3c2a2d81603031ea3` | Conditional Makefile readability closeout: honest no-op. The source list and Makefile remained byte-for-byte unchanged because no non-redundant grouping comment was justified. |
Batch 10 is not counted among the nine core source workflows. Batch 09 did
produce a precise cleanup diff, while Batch 10 records the required review
without manufacturing source churn.
## Static review
The final static review found no source-blocking issue. In particular, it did
not find a regression in declaration/definition matching, include ownership or
graph shape, descriptor indices and callback ownership, errno paths, resource
release, in-memory ABI/layout, endian decoding, ACL construction, macro values,
or the byte-identical xattr function move.
The important proofs were:
- The three semantic scalar aliases are direct unsigned 64-bit aliases; the
approved substitutions preserve size, alignment, signedness, structure
layout, parameter representation, and arithmetic width.
- The three `le16dec`/`le32dec` inputs are raw little-endian disk bytes of the
required length; the decoded values are equivalent while avoiding typed
unaligned loads.
- ACL owner/group/other conversion was exhaustively compared across modes
`0000` through `0777`: `1536/1536` tag/mode combinations were equivalent,
with zero mismatches.
- The compression include graph remained acyclic and each migrated or private
symbol retained one authoritative declaration/definition relationship.
- The six codec descriptor tuples, indices, names, config callbacks,
decompress callbacks, and ZSTD conditional ownership remained equivalent.
- The removed ZSTD lower bound is unreachable because the field is `uint8_t`;
its promoted value plus 10 cannot be less than 10.
- Macro expressions and values were unchanged, and the deleted chunk-format
alias had no source, build, documentation-interface, or preprocessor
consumer in the target tree.
- The moved xattr function block retained its exact SHA-256 and conditional
context.
The Batch 06 record originally used wording that could imply an actual
`WITH_ZSTDIO=0` build had already occurred during that batch. It has been
corrected to state that only the static conditional path was validated then.
Final validation subsequently built both configurations successfully.
## KLD build matrix
The valid final run used FreeBSD `15.0-RELEASE-p8`, amd64, with `/usr/src/sys`
and the in-tree FreeBSD ZSTD headers present. The extracted source archive
matched SHA-256
`bb7e7f0383be29102475575e130e0b19a48452d20520c94059dc6ef128d0c7ce`.
For each configuration, the guest executed:
```sh
timeout -k 10s 120s make "WITH_ZSTDIO=<0-or-1>" clean
timeout -k 10s 600s make "WITH_ZSTDIO=<0-or-1>"
```
| Configuration | Clean exit | Build exit | Module SHA-256 |
| --- | ---: | ---: | --- |
| `WITH_ZSTDIO=0` | 0 | 0 | `96dc276c6c3f68943e68a144cc2ea138ffc215b62c7392cad9688ec45c9fc2fb` |
| `WITH_ZSTDIO=1` | 0 | 0 | `4d879c0b7ebc653e915a3e41863f74d6d5b5f18aa81c5178579368048d1b6ee3` |
The enabled build compiled `decompressor_zstd.c` with `-DZSTDIO` and linked
`erofs.ko`. These builds prove both compile-time configurations close and link;
they do not prove ZSTD or DEFLATE data-path correctness.
## KLD and smoke results
The valid run loaded the `WITH_ZSTDIO=1` artifact under the standard module
name `erofs.ko`.
| Check | Result |
| --- | --- |
| `kldload` | exit 0 |
| `kldstat -n erofs.ko` | present as `erofs.ko`, module id 5 |
| Loaded module SHA-256 | `4d879c0b7ebc653e915a3e41863f74d6d5b5f18aa81c5178579368048d1b6ee3` |
| `kldunload erofs.ko` | exit 0 |
| Post-unload `kldstat` | module absent, as expected |
All ordinary read cases matched their complete expected hashes:
| Case | Exit | Expected SHA-256 | Actual SHA-256 | Elapsed |
| --- | ---: | --- | --- | ---: |
| Plain | 0 | `056f8f7585667dc695e2edf936deaf84cfee0f88671ba6cd5be22ce890763433` | `056f8f7585667dc695e2edf936deaf84cfee0f88671ba6cd5be22ce890763433` | 593 ms |
| LZ4 | 0 | `3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880` | `3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880` | 6822 ms |
| LZMA | 0 | `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461` | `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461` | 3418 ms |
The dmesg delta was zero bytes and the suspect-filter output was zero bytes.
No new panic, trap, or EROFS error was observed.
## Evidence and invalid attempts
The only final valid evidence directory is:
```text
/work/tests-dev/temp/pre12-smoke-direct-20260813T165639Z/
```
Its `final-summary.txt`, guest build logs, module hashes, case records, dmesg
files, and cleanup record form the accepted evidence set.
Two earlier infrastructure attempts are explicitly invalid and contribute no
source result and no test pass:
1. `/work/tests-dev/temp/pre12-build-20260813T154900Z/` is **INFRA-FAIL**.
The SSH password expanded to an empty string, so there were zero successful
SSH sessions and neither build began. Its QEMU and temporary resources were
cleaned. This is neither a DUT failure nor a test success.
2. `/work/tests-dev/temp/pre12-final-smoke-20260813T160957Z/` stopped while
redundantly hashing the 16 GB base image. Five nested Codex CLI sessions
were discovered and all were terminated or had exited; no QEMU, SSH,
guest build, KLD action, or smoke case started. Its partial files are not
test evidence and it is neither a DUT failure nor a test success.
The final accepted run was performed by a single direct execution layer and
did not start a nested agent or Codex process.
One intermediate runner revision attempted to query KLD using the renamed
artifact filename rather than the module's standard name. That was a
test-period runner issue, not a DUT issue. Only the temporary runner copy under
`tests-dev/temp` was corrected: the successful `WITH_ZSTDIO=1` artifact was
copied to `erofs.ko`, then the complete KLD and Plain/LZ4/LZMA sequence was
rerun. No `repo-pre-12` source or build file was modified to make the test
pass.
## Cleanup and repository state
The accepted run completed cleanup successfully:
- Owned QEMU stopped.
- SSH ControlMaster stopped.
- Test port `10048` released.
- Test overlay removed.
- Guest test mounts and `md70`/`md71`/`md72` devices were removed.
- The retained guard QEMU remained unchanged and reachable at PID `26318`,
port `9222`, with process start ticks `1102996924`.
- The base image remained mode `0444`, size `16515530752` bytes, and mtime
`1786418212`. Its retained SHA-256 record is
`67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef`;
the final run intentionally did not repeat the expensive 16 GB hash.
- At validation completion, `HEAD` and `xdm/main` both identified
`a55e42a117e504f6ece49be3c2a2d81603031ea3`, and the worktree was clean.
This report is documentation-only and does not alter the validated DUT source
tree `d63c45c7872dcf9dd84007c8883cf9fbe0f9467d`.
## Deferred feature work
The following issues remain unresolved and require dedicated behavior design,
fixtures, or feature tests before any future implementation can be accepted:
- inode decode split or rewrite;
- map request interface conversion;
- directory/namei control flow, qstr, cookies, namecache, and lock behavior;
- metadata reader consolidation;
- rejection or handling of a non-directory root;
- `run_len == 0` behavior;
- header-only xattr behavior;
- duplicate or missing `device.N` behavior;
- `insmntque` race closure;
- explicit extent and explicit mapped-extent fixtures;
- `vnode_create_vobject` errno behavior.
No claim is made that these issues were fixed or covered by the ordinary
smoke run. The final PASS is limited to the Pre12 static review, both KLD build
configurations, KLD lifecycle, and the Plain/LZ4/LZMA smoke cases recorded in
the accepted evidence directory.
+10
View File
@@ -0,0 +1,10 @@
# Pre13 Baseline
- Source commit: `35eb53c65a4f75ba580f64af043779b930e3bb78`
- `repo-pre-12` tree: `4ea4d7cfbf8b8a884b291f7433e19ce8e5af5a11`
- `repo-pre-12/src` tree: `d63c45c7872dcf9dd84007c8883cf9fbe0f9467d`
- `repo-pre-13/src` tree after copy: `d63c45c7872dcf9dd84007c8883cf9fbe0f9467d`
- Copy verification: 327 files, 43 directories, and 0 symbolic links matched by relative path, mode, link target, and SHA-256 content manifest before this document was added.
- Copy manifest SHA-256: `c6bdcee893a45a03308897715062aada41c591a82432f2c0008dbd316d10cf07`
Except for this baseline document, `repo-pre-13` is an exact copy of `repo-pre-12`.
+90
View File
@@ -0,0 +1,90 @@
# Pre13 Execution Status
## Final Status
Pre13 is **scoped PASS** for the planned and admitted Pre13 work at
`b804d7b77d1c76b8e844dc4a758c6ab3381ca006`. This is not a full feature-suite
PASS and does not claim V02 positive coverage.
At status backfill time, DUT `HEAD`, local `main`, fetched `xdm/main`, remote
`refs/heads/main`, and the DUT worktree all resolve to
`b804d7b77d1c76b8e844dc4a758c6ab3381ca006`; the DUT worktree is clean.
## Completed Source Work
- Low-risk alignment commits:
- L-A1 helper/guard alignment:
`2d0041ba3a195c184a52bdccae3bd00af520d4b7`
- L-A2 helper ordering:
`190a0b908d25e62b5c78ba82377f53d359fc5c4f`
- L-B semantic type alignment:
`f236836a55ad7ec7d536df483460ea394e38b6c3`
- L-C1 on-disk endian field typing:
`217b7a3032ec917c5ea63652b0f3cad734990d48`
- L-C2 flexible on-disk arrays:
`e72ecd7cc024047f0ce7c1855e6e44ae7f5350e1`
- L-D LZ4 endian helper:
`fe338f701059de8dfd6336dea7f9288bc0f12e9f`
- L-E localized private constants:
`9028b92343ec7f5bbc1b07477b8d4a669c436955`
- Stage0 decisions:
`8ed80c3ddbedc64b8b80daf1be16b28c4f24f10d`.
- H04 root-type validation:
`ee9dc4715436eeee21ae2d84eab2677b27d87ddd`.
- H03 qstr lookup alignment:
- H03a bounded qstr comparator:
`f51b0fe1ceb27cac641667aa5688d371a60d2d99`
- H03b lookup search propagation:
`3313d305a2193ef6ffa26c6a189e47187b9c0d70`
- H03c VOP adapter:
`83a5bef73b329c967bd8390a0966d30005295758`
- Build/test-input fixups:
`dc47e8f99c947d2929d3528a6c139a85eb4251d5`,
`b804d7b77d1c76b8e844dc4a758c6ab3381ca006`.
## Stage0 Decisions
- H04 ROOT-NONDIR: `READY`, then implemented and custom validated.
- H07 ZERO-RUN: `STOP/no-op`; 1,360 legal states showed no reachable
zero-progress path.
- H01a/b/c map request work: `STOP`; the tuple oracle lacked chunk,
multidevice, bounds, and overflow coverage.
- H03 comparator: `READY`; Python 11,562 cases and C 1,048,576 cases passed.
- Device-option probing: blocked/non-gating, no behavior claim.
## Validation Status
- Targeted build/layout/probe retry:
`/work/tests-dev/temp/pre13-targeted-final-runnerfix-retry-20260814T0138Z`.
`WITH_ZSTDIO=0` and `WITH_ZSTDIO=1` builds passed with module SHA256
`0b1244cf9b7fe70649bdcc1c6ef12e6151c77b10c2755b366f960b101ccb7220`
and `15db1fbf9450259d030e88602e1c5ba2e452a731c641b359bd5a2d7823da2735`.
Layout probe and four userspace probe builds passed.
- H04 custom fixture: PASS. Valid root mounted/remounted; non-directory root
returned exact `EINTEGRITY` (`97`); failed mount left the provider
detachable.
- H03 targeted feature cases: PASS for TC042, TC043, TC044, TC045, TC053,
TC054, TC123, TC141, and TC148. TC141's raw runner failure was an extra
empty `umount ''` false negative after 25 real commands passed. TC148
covered 320 files, 322 dirents, content, and empty suspect dmesg.
- Final smoke:
`/work/tests-dev/temp/pre13-missing-and-smoke-20260814T021330Z/final-summary.md`.
Plain, LZ4, and LZMA passed; KLD load/stat/unload and cleanup passed.
- V01 race:
`/work/tests-dev/temp/pre13-v01-race-20260814T031032Z/retry-fresh-20260814T032500Z`.
Raw runner FAIL was reclassified by the final offline analyzer at tests-dev
`22c0bea73b9b81cc44385d1ba7103fa820c56070` as
`DUT PASS / TEST false-negative-corrected`. The four offline analyzer unit
tests passed. Dmesg total delta was 24 lines / 1,266 bytes with suspect
bytes 0.
## Explicit Non-Claims
- No full feature suite was run.
- No V02 positive explicit-extent fixture was produced; V02 remains PARTIAL.
- TC004, TC011, TC143, TC144, TC145, TC150, and other unlisted feature cases
are not claimed as passing.
- H01, H07, H05, H06, H02, and H08 source changes were intentionally stopped,
rejected, or no-op as recorded in the final report.
Detailed evidence and limits are recorded in `pre13-final-report.md`.
+344
View File
@@ -0,0 +1,344 @@
# Pre13 Final Report
## Conclusion
Pre13 is **scoped PASS** for the planned and admitted Pre13 work:
- DUT baseline snapshot: `a587d489b829ef8e7fac94c87130b61d47aa3e44`.
- Final DUT commit: `b804d7b77d1c76b8e844dc4a758c6ab3381ca006`.
- At final report time, `HEAD`, local `main`, fetched `xdm/main`, remote
`refs/heads/main`, and the only DUT worktree all resolve to
`b804d7b77d1c76b8e844dc4a758c6ab3381ca006`.
- DUT working tree is clean.
This is **not** a full-filesystem certification. No full 161-case feature
suite was run, no positive V02 explicit-extent fixture was produced, and Pre13
does not claim that unrun cases such as TC004, TC011, TC143, TC144, TC145,
TC150, or other non-targeted feature cases passed.
## Commit Inventory
The audited DUT range is
`a587d489b829ef8e7fac94c87130b61d47aa3e44..b804d7b77d1c76b8e844dc4a758c6ab3381ca006`.
### Planning and Stage0
- `76a4c82`, `fd300cb`, `2f487f0`: Pre13 planning documents and low-risk batch
accounting.
- `8ed80c3ddbedc64b8b80daf1be16b28c4f24f10d`: recorded Stage0 gate decisions
in `repo-pre-13/docs/pre13-stage0-decisions.md` and the initial execution
status.
### Low-Risk Source Alignment
- `2d0041ba3a195c184a52bdccae3bd00af520d4b7`: L-A1 helper and guard alignment
across `compress.h`, `data.c`, `erofs_fs.h`, and `inode.c`, with
`pre13-low-a1.md`.
- `190a0b908d25e62b5c78ba82377f53d359fc5c4f`: L-A2 local helper ordering in
`decompressor.c` and `xattr.c`, with `pre13-low-a2.md`.
- `f236836a55ad7ec7d536df483460ea394e38b6c3`: L-B semantic `erofs_nid_t` and
`erofs_off_t` alignment across data, directory, inode, namei, super, xattr,
zdata, and zmap paths, with `pre13-low-b.md`.
- `217b7a3032ec917c5ea63652b0f3cad734990d48`: L-C1 on-disk `__leXX` field
alignment in `erofs_fs.h` and initial `pre13_ondisk_layout_probe.c`, with
`pre13-low-c1.md`.
- `e72ecd7cc024047f0ce7c1855e6e44ae7f5350e1`: L-C2 flexible on-disk arrays
and layout probe updates, with `pre13-low-c2.md`.
- `fe338f701059de8dfd6336dea7f9288bc0f12e9f`: L-D LZ4 offset endian helper in
`decompressor_lz4.c`, with `pre13-low-d.md`.
- `9028b92343ec7f5bbc1b07477b8d4a669c436955`: L-E localized private
constants, removed `erofs_defs.h`, and updated consumers/docs.
### Behavior Changes and Fixups
- `ee9dc4715436eeee21ae2d84eab2677b27d87ddd`: H04 rejects non-directory root
inodes during mount, with `pre13-h04-root-validation.md`.
- `f51b0fe1ceb27cac641667aa5688d371a60d2d99`: H03a introduced the bounded
qstr comparator.
- `3313d305a2193ef6ffa26c6a189e47187b9c0d70`: H03b passed qstr through block
and dirent lookup search.
- `83a5bef73b329c967bd8390a0966d30005295758`: H03c adapted VOP lookup to the
bounded qstr path.
- `dc47e8f99c947d2929d3528a6c139a85eb4251d5`: fixed const-correct build inputs
and the userspace layout probe `bool` include issue.
- `b804d7b77d1c76b8e844dc4a758c6ab3381ca006`: retired stale decompression test
harnesses that no longer matched the current module surface; this did not hide
a DUT test failure.
## Stage0 Gate Results
Stage0 was a mixed gate result, not an overall PASS:
- Gate A, ROOT-NONDIR: **READY**. The baseline accepted a non-directory root;
Pre13 froze the rejection as `EINTEGRITY` and allowed H04.
- Gate B, ZERO-RUN: **STOP/no-op**. The analysis covered 1,360 legal states and
found no reachable zero-progress state for H07.
- Gate C, map tuple oracle: **STOP**. The oracle covered only 26 plain,
inline, and hole tuples; it did not cover chunk, multidevice, bounds, or
overflow behavior, so H01a/H01b/H01c were not implemented.
- Gate D, qstr comparator: **PASS/READY**. The Python comparator corpus passed
11,562 cases and the independent C harness passed 1,048,576 cases.
- Gate E, device option probe: **blocked/non-gating**. It produced no runtime
behavior claim and did not block Pre13.
## Intentional Stops and Rejects
- H01 map objectization: **STOP** because the tuple oracle was incomplete for
chunk, multidevice, bounds, and overflow coverage.
- H07 zero-run: **STOP/no-op** because 1,360 legal states produced no reachable
zero-progress path.
- H05 header-only xattr: **rejected**; Linux treats the format as undefined and
BSD already returns `EOPNOTSUPP`.
- H06 duplicate `device.N`: **rejected/no-op** for source changes because
`vfs_sanitizeopts()` makes the EROFS duplicate-option check unreachable; any
probe is non-gating.
- H02 inode split: **rejected** because it would diverge from Linux's single
`erofs_read_inode()` switch and create low-value churn.
- H08 pager errno propagation: **rejected** because the target
`vnode_create_vobject()` path observably returns 0.
- V02 explicit extent positive coverage: **PARTIAL**. No positive mapped
fixture was available, and TC157 negative coverage is not a substitute.
## Build, Layout, and Probe Evidence
Primary evidence:
`/work/tests-dev/temp/pre13-targeted-final-runnerfix-retry-20260814T0138Z`.
After `dc47e8f` and `b804d7b`, the targeted retry produced:
- `WITH_ZSTDIO=0` build: PASS, module SHA256
`0b1244cf9b7fe70649bdcc1c6ef12e6151c77b10c2755b366f960b101ccb7220`.
- `WITH_ZSTDIO=1` build: PASS, module SHA256
`15db1fbf9450259d030e88602e1c5ba2e452a731c641b359bd5a2d7823da2735`.
- Loaded module in that targeted run: SHA256
`15db1fbf9450259d030e88602e1c5ba2e452a731c641b359bd5a2d7823da2735`.
- Userspace layout probe: build and run return code 0.
- Four userspace probe builds: `g3_vfs_probe`, `mount_errno_probe`,
`nfs_fh_tool`, and `readdir_probe` all returned 0.
The first targeted runner attempt still ended host-side FAIL because TC141
included an extra empty `umount ''` command. That was classified as a test
runner false negative, not a DUT failure: the 25 real TC141 commands all had
return code 0, and only the 26th synthetic empty unmount failed.
## H04 Root Validation
Evidence path:
`/work/tests-dev/temp/pre13-targeted-final-runnerfix-retry-20260814T0138Z/guest-evidence/cases/H04`.
H04 custom fixture result: **PASS**.
- Valid root fixture SHA256:
`d0baba501ae12fcdb8869a98908486473d2bcfb6b332a47cb6a6c1f856a3ab34`.
- Non-directory root fixture SHA256:
`60bf0d0d93baabdfa23b71f48283d317f6b7218852f3d19aee77575c41500db7`.
- Root proof payload SHA256:
`0ebc44a6c5a02ba458d1a115bc338fa4acb4d973a37804c9b926eafc0c53575f`.
- Valid root mounted, root mode was `040755`, payload matched, and unmount plus
`md` detach succeeded.
- Non-directory root returned exact `mount_errno=97` (`EINTEGRITY`) with message
`erofs: root inode nid=36 is not a directory`; the failed mount left the
provider detachable.
- The valid fixture remounted successfully after the non-directory failure.
- Per-case dmesg delta and suspect logs were empty.
TC011 and TC150 were not run and are not claimed as passing. They are not
Pre13 blockers because the dedicated H04 fixture directly exercised the actual
root-type change, including the valid control, exact errno, detach, and remount
properties.
## H03 Targeted Feature Evidence
H03 targeted cases exercised the qstr lookup changes and passed for:
TC042, TC043, TC044, TC045, TC053, TC054, TC123, TC141, and TC148.
Evidence:
- TC042/043/044/045/053/054/123 and TC141 raw evidence:
`/work/tests-dev/temp/pre13-targeted-final-runnerfix-retry-20260814T0138Z`.
- TC148 valid copied evidence and final smoke summary:
`/work/tests-dev/temp/pre13-missing-and-smoke-20260814T021330Z/final-summary.md`.
TC141 details:
- The runner logged 26 commands, but the last was the invalid empty
`umount ''` cleanup bug.
- The 25 real commands passed: valid-base and valid-padding cold lookup/read,
repeated missing lookups returning `ENOENT`, `entries=322` wide-directory
checks, and three corrupt lookup fixtures returning repeated
`EINTEGRITY` (`errno=97`).
- The only failure was the empty unmount runner command. Its stderr was
`statfs: No such file or directory` / `unknown file system`, with no DUT
dmesg delta.
TC148 details:
- Outcome: PASS.
- Fixture SHA256:
`9a94e9af2cab264b9c11975a20d78e615d6fe1e6f86267173cb5ac86aecb2b17`.
- Mount command return code: 0.
- Expected and actual regular-file count: 320.
- Directory oracle: 322 entries, with two directories and 320 regular files.
- `d_off` restart count and `seekdir` restart count: 322 each.
- Target content matched.
- Per-case dmesg delta and suspect log were empty.
## Final Smoke Evidence
Evidence path:
`/work/tests-dev/temp/pre13-missing-and-smoke-20260814T021330Z/final-summary.md`.
Final smoke outcome: **PASS**.
- DUT archive SHA256:
`d0c052bfef024d0acb40e107800cc8e8227fab6b691e8825af5cf01f19672fe4`.
- Assets SHA256:
`a9741ea468493bdc72d399615892d7f6269226467d09fa6613fa01352f011908`.
- Probe bundle SHA256:
`7b10d23c8a780fb35810fe85626305a8c49f196d270f98e5224045bd79f51066`.
- Clean tests-dev worktree commit used for the smoke:
`f86799654a37cdb81025b9e703635f7c7812fecb`.
- `WITH_ZSTDIO=0` build: PASS, module SHA256
`0b1244cf9b7fe70649bdcc1c6ef12e6151c77b10c2755b366f960b101ccb7220`.
- `WITH_ZSTDIO=1` build: PASS, module SHA256
`15db1fbf9450259d030e88602e1c5ba2e452a731c641b359bd5a2d7823da2735`.
- Loaded module for final smoke: `WITH_ZSTDIO=0`, SHA256
`0b1244cf9b7fe70649bdcc1c6ef12e6151c77b10c2755b366f960b101ccb7220`.
- `kldload`, `kldstat -n erofs.ko`, `kldunload`, and final KLD cleanup: PASS.
- Plain fixture SHA256:
`3785ca07e7bd16f6c611191596ae0314253c0ae9b7217a4b22de25799b0f08ac`;
payload SHA256 matched
`056f8f7585667dc695e2edf936deaf84cfee0f88671ba6cd5be22ce890763433`.
- LZ4 fixture SHA256:
`967cc1b625546f9f9f881472e71cc3d849c65feb4e98e67fbfdc9b90a4be6dda`;
payload SHA256 matched
`3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880`.
- LZMA fixture SHA256:
`32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9`;
payload SHA256 matched
`ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`.
- Every attach, mount, hash, unmount, and detach command returned 0.
- Per-case and global dmesg suspect logs were empty.
- Global mount, md, KLD, QEMU, ControlMaster, overlay, and ports
`10056`, `10057`, `10058` cleanup passed.
- Guard QEMU PID `26318` and port `9222` were unchanged and reachable.
- The main tests-dev worktree was dirty before and after with unchanged diff
SHA256 `92d3808ab33ff9b22fa2ee3f8a854c1fc36effecb4dd6c0917ae7e80c57fc23a`;
the clean tests worktree, not the dirty main worktree, was used as evidence.
The final summary explicitly states that no other TC, full feature suite,
TC004, TC143, TC144, or TC145 was run. Pre13 preserves that limitation.
## V01 Race Evidence
Raw evidence:
`/work/tests-dev/temp/pre13-v01-race-20260814T031032Z/retry-fresh-20260814T032500Z`.
tests-dev evidence commits were verified on `xdm/main`:
- `2269908cc3f721d57ae8138faef664a6bc4c7cc9`: added the V01 concurrent unmount
race runner.
- `374c9d8473e6638fa0c8e865fcfcfee141b13f0d`: fixed runner SSH command
execution.
- `22c0bea73b9b81cc44385d1ba7103fa820c56070`: corrected forced-unmount deadfs
errno classification and added the offline analyzer/unit tests. Fetched
`xdm/main` and remote `refs/heads/main` both resolve to this commit.
Original raw run:
- Raw host/guest outcome: FAIL, `guest race failed rc=41`.
- DUT commit: `b804d7b77d1c76b8e844dc4a758c6ab3381ca006`.
- Module SHA256:
`15db1fbf9450259d030e88602e1c5ba2e452a731c641b359bd5a2d7823da2735`.
- Fixture root-valid SHA256:
`d0baba501ae12fcdb8869a98908486473d2bcfb6b332a47cb6a6c1f856a3ab34`.
Normal group:
- Duration 90 seconds, 8 workers, 154 loops.
- `umount` returned accepted `EBUSY` 154 times; no successful unmount/remount.
- Worker read/stat/open/fstat success counts were each 79,164.
- Worker content mismatch, short reads, rejected errors, and dmesg suspect bytes
were all 0.
Forced group:
- Duration 90 seconds, 8 workers, 125 loops.
- Successful forced unmount/remount cycles: 125/125.
- `stat ENOENT`: 102,165; `open ENOENT`: 16.
- Raw rejected errors: 1,772, consisting of 882 `fstat EBADF` and 890
`read ENXIO`.
- The final mounted SHA matched the expected payload SHA in both normal and
forced groups.
Final analyzer result from
`scripts/analyze-pre13-v01-race-evidence.py` at
`22c0bea73b9b81cc44385d1ba7103fa820c56070`:
- `normal_verdict=PASS`.
- `forced_verdict=PASS`.
- `forced_expected_deadfs_errors=1772`.
- `forced_expected_deadfs_fstat_ebadf=882`.
- `forced_expected_deadfs_read_enxio=890`.
- `dut_verdict=PASS`.
- `test_verdict=false-negative-corrected`.
- `overall_verdict=DUT PASS / TEST false-negative-corrected`.
The `EBADF` and `ENXIO` results occur only after successful forced unmounts,
when existing descriptors are taken over by `deadfs`; that is standard FreeBSD
semantics and not a DUT instability.
The analyzer's four offline unit tests all passed:
- forced deadfs errors are corrected;
- normal deadfs errno is rejected;
- forced unrelated errno is rejected;
- count mismatch is rejected.
V01 cleanup and integrity:
- QEMU stopped, V01 port released, overlay removed.
- Base image mode, size, and mtime remained `444`, `16515530752`, and
`1786418212`.
- Guard VM was unchanged and reachable.
- Guest mount/md/KLD owned-resource cleanup passed.
- Total dmesg delta was 24 lines / 1,266 bytes, all known non-suspect
`mangled entry` lines; suspect bytes were 0. This is not a zero-total-delta
run.
- The dirty main tests-dev worktree was unchanged; the independent clean
worktree was clean.
## Static Review and Evidence Boundaries
Final static review of the Pre13 source delta found no production-source
blocker, test backdoor, out-of-bounds issue, ABI/layout issue,
lock/lifecycle issue, or errno issue requiring another DUT fix. The one
dynamic item that was pending at that review point was documentation/status
backfill; that was a reporting-sequence issue, not an unperformed test gate.
The source/test diff boundary was also reviewed:
- Production changes are limited to the planned low-risk alignment work, H04,
H03, and build-input fixups.
- The only new tracked test asset in the DUT repo is the userspace layout probe.
- The stale decompression harness removals in `b804d7b` were documented and do
not hide a failing Pre13 DUT test.
## Unrun or Non-Claimed Coverage
Pre13 deliberately does **not** claim:
- full feature-suite PASS;
- TC004 PASS;
- TC011 or TC150 PASS;
- TC143, TC144, or TC145 PASS;
- TC146/V02 positive explicit-extent PASS;
- H01 map objectization behavior;
- H07 zero-progress behavior;
- H05/H06/H02/H08 source behavior changes.
The custom H04 fixture directly covers the actual H04 source change, so the
absence of TC011/TC150 is not a blocker for the scoped Pre13 result. Likewise,
the final smoke covers Plain/LZ4/LZMA smoke only; Deflate, ZSTD, partial, and
other codec feature cases remain outside the Pre13 claim unless specifically
listed above.
+30
View File
@@ -0,0 +1,30 @@
# Pre13 H03a: bounded qstr comparator carrier
## Scope
- Added a private `struct erofs_qstr` to `src/namei.c` with explicit `name`
and `end` pointers.
- Converted `erofs_dirnamecmp()` to consume bounded qstr objects.
- Kept the existing search helpers on their original pointer/length interface
through a temporary local adapter. Block search, VOP lookup, vnode locking,
namecache timing, errno handling, readdir, and cookies are unchanged.
## Static equivalence
- Search and disk lengths are still derived from the same pointer ranges.
- `matched` is clamped to both ranges before comparison.
- Comparison remains unsigned byte ordering, supports non-NUL search keys and
embedded NUL bytes, and never assumes a trailing NUL at the disk boundary.
- The adapter performs no allocation, copying, normalization, or error mapping.
## Comparator gate
Tests baseline: `ce03c283e6e2cb8f1b69c8eff21f7d06098a4fbe`.
- Python deterministic corpus: PASS, 11,562 cases.
- Independent C harness: PASS, 1,048,576 cases.
These host comparator results prove the old and new comparison contracts are
equivalent for the generated corpus. They are not dynamic filesystem feature
test results. H03 still requires TC042, TC043, TC044, TC045, TC053, TC054,
TC123, TC141, and TC148, followed by the final smoke test.
+39
View File
@@ -0,0 +1,39 @@
# Pre13 H03b: qstr block and dirent search
## Scope
- Converted `find_target_dirent()` and `erofs_find_target_block()` to accept a
bounded `struct erofs_qstr` search key.
- Constructed bounded on-disk qstr objects from the same validated dirent name
offsets and block end pointers used before this change.
- Constructed a temporary search qstr inside `erofs_namei()` while retaining
its original pointer/length external interface for H03c.
- Removed the H03a legacy comparator adapter.
No changes were made to `dir.c`, readdir, cookies, buffer ownership, directory
validation, binary-search decisions, errno selection, cleanup, VOP lookup,
vnode locking, or namecache operations.
## Static evidence
- `erofs_dirnamecmp()` has exactly two call sites: the block-level first-name
comparison and the candidate block's dirent comparison.
- Both on-disk qstr end pointers use the same next `nameoff` or validated block
boundary as the previous pointer arguments.
- `erofs_find_target_block()` retains every `erofs_brelse()` path, candidate
replacement, `EINTEGRITY` assignment, and `ENOENT` distinction.
- `erofs_namei()` still releases the selected block once and returns `ENOENT`
only for a true miss.
- The `erofs_lookup()` control flow and all `cache_enter()`, `vn_vget_ino()`,
`erofs_vget()`, and `cn_lkflags` uses are outside this stage's diff.
## Comparator gate
Tests baseline: `ce03c283e6e2cb8f1b69c8eff21f7d06098a4fbe`.
- Python deterministic corpus: PASS, 11,562 cases.
- Independent C harness: PASS, 1,048,576 cases.
The comparator gates are host static-equivalence evidence, not dynamic feature
tests. H03 still requires TC042, TC043, TC044, TC045, TC053, TC054, TC123,
TC141, and TC148, followed by the final smoke test.
+36
View File
@@ -0,0 +1,36 @@
# Pre13 H03c: qstr VOP lookup adapter
## Scope
- Changed the private `erofs_namei()` interface to accept the bounded qstr
carrier directly.
- Moved search qstr construction to the `erofs_lookup()` FreeBSD VOP adapter,
using exactly `cn_nameptr` and `cn_namelen` after the existing length checks.
- Removed the temporary qstr construction from `erofs_namei()`.
No changes were made to `componentname` validation, mutation rejection, `.` or
`..` handling, vnode lock mode, `vn_vget_ino()`, `erofs_vget()`, namecache
timing, errno mapping, readdir, directory cookies, or directory UIO handling.
## Static evidence
- The qstr search range is `[cn_nameptr, cn_nameptr + cn_namelen)` and does not
require or inspect a trailing NUL.
- The only `erofs_namei()` caller remains `erofs_lookup()`.
- A true `ENOENT` remains the only result eligible for negative `cache_enter()`;
`EINTEGRITY` and all other errors still return directly.
- `ISDOTDOT` still selects `vn_vget_ino()` with the original `cn_lkflags`;
ordinary hits still select `erofs_vget()` with the same lock flags.
- The H03c diff does not modify any `cache_enter()`, vnode lookup, lock, cookie,
readdir, or UIO statement.
## Comparator gate
Tests baseline: `ce03c283e6e2cb8f1b69c8eff21f7d06098a4fbe`.
- Python deterministic corpus: PASS, 11,562 cases.
- Independent C harness: PASS, 1,048,576 cases.
The comparator gates are host static-equivalence evidence, not dynamic feature
tests. Dynamic H03 validation remains pending for TC042, TC043, TC044, TC045,
TC053, TC054, TC123, TC141, and TC148, followed by the final smoke test.
+44
View File
@@ -0,0 +1,44 @@
# Pre13 H04 Root Inode Validation
## Scope
H04 adds one mount-time format check in `src/super.c`: the decoded root inode
must have FreeBSD vnode type `VDIR`.
The check runs after packed and metabox inode initialization and before xattr
prefix initialization or publication of `mp->mnt_data`. No vnode is created and
no permanent root inode reference is retained.
## Error Contract
A decodable non-directory root violates the EROFS filesystem structure. The
mount therefore fails with positive FreeBSD errno `EINTEGRITY`.
This intentionally differs from Linux's negative `EINVAL` return while keeping
the same format rejection. `EINTEGRITY` matches the existing FreeBSD EROFS
contract for decodable on-disk metadata that violates filesystem invariants.
## Ownership Review
- The temporary `struct erofs_node` is stack-owned and has no independent
allocations or vnode references.
- Decode failures and the type rejection use the existing `fail` path.
- `erofs_sb_free()` releases extent-cache state, internal inodes, external
devices, and the primary GEOM device in the established reverse order.
- The failure occurs before xattr prefixes, mount data, mount flags, or a root
vnode are published.
- The legal directory-root path is unchanged after the new check.
## Validation Gate
The required targeted QEMU validation is:
1. Build and load the current DUT module.
2. Mount the valid paired fixture, verify mode `040755`, read its proof file,
and unmount it.
3. Mount the non-directory-root fixture and require exact errno `EINTEGRITY`.
4. Verify mount, md, KLD, and GEOM resources return to zero, then remount the
valid fixture.
5. Verify the kernel log has no panic, trap, or lock warning.
No full feature suite is part of H04.
+21
View File
@@ -0,0 +1,21 @@
# Pre13 L-A1
This batch aligns the remaining low-risk helper vocabulary with Linux:
- `compress.h` now uses the Linux include guard name.
- `erofs_inode_is_data_compressed()` replaces four equivalent FULL/COMPACT
tests that do not need to distinguish the two layouts.
- `erofs_addrmask()` directly uses the existing 48-bit feature helper.
Static proof:
- For every value from zero through `EROFS_INODE_DATALAYOUT_MAX`, the new
compressed-layout helper equals the replaced boolean expression.
- The helper was not substituted in `zmap.c`, where FULL and COMPACT remain
distinct cases.
- `erofs_is_48bit()` and `erofs_sb_has_48bit()` tested the same incompat bit;
address-mask values and control flow are unchanged.
- The old include guard and duplicate 48-bit helper have no remaining source
consumers.
No build, QEMU, smoke, or feature test was run for this batch.
+14
View File
@@ -0,0 +1,14 @@
# Pre13 L-A2
This batch mechanically aligns two local declaration groups with Linux:
- The xattr prefix cleanup/init functions follow the public get/list entry
points and precede the ACL entry point.
- The shifted/interlaced decompressor descriptors precede the LZ4 descriptor.
Only complete blocks moved. No prototype, function body, callback, conditional
compilation, or indexed descriptor slot changed. Pre-move and post-move hashes
of both xattr functions and all three descriptor definitions are identical;
the indexed descriptor table is byte-identical.
No build, QEMU, smoke, or feature test was run for this batch.
+16
View File
@@ -0,0 +1,16 @@
# Pre13 L-B
This batch completes the planned semantic aliases for internal EROFS NIDs and
byte offsets. Each changed object was selected from the Pre13 variable-level
whitelist.
The aliases remain unsigned 64-bit types. No expression, cast, format string,
error path, field order, or control flow changed. File sizes, lengths, chunk
and extent indexes/counts, directory cookies, media sizes, timestamps, disk
fields, and overflow-only temporaries retain their previous types.
Declarations and definitions were updated together. The resulting source diff
contains only type tokens, line wrapping required by those tokens, and this
record.
No build, QEMU, smoke, or feature test was run for this batch.
+19
View File
@@ -0,0 +1,19 @@
# Pre13 L-C1
This batch aligns the endian type vocabulary of named multi-byte on-disk
fields with the corresponding Linux EROFS declarations.
Only fields for which the same Linux structure and field use `__le16`,
`__le32`, or `__le64` were changed. Byte fields, arrays, field order, unions,
packing, macros, and all read/conversion sites remain unchanged. The direct
disk-type parameter of `erofs_xattr_ibody_size()` was synchronized.
The FreeBSD compatibility aliases map each `__leXX` type to the exact previous
unsigned integer type. A host-side probe compared 119 structure/union
size/alignment and named-field offset entries before and after the batch; all
values were identical. `tests/pre13_ondisk_layout_probe.c` retains legacy
definitions and exhaustive assertions for every modified structure so the
same proof can be compiled with the final FreeBSD guest toolchain. Existing
kernel `_Static_assert` checks remain unchanged.
No KLD build, QEMU, smoke, or feature test was run for this batch.
+14
View File
@@ -0,0 +1,14 @@
# Pre13 L-C2
The two planned zero-length on-disk tail arrays now use standard flexible array
declarators:
- `erofs_xattr_ibody_header::h_shared_xattrs`
- `erofs_xattr_long_prefix::infix`
The layout probe retains the previous `[0]` declarations and asserts equal
structure size/alignment and member offsets. Host Clang accepts both old and
new declarations with the same warning/error result and identical layouts.
The probe is also retained for the final FreeBSD guest compiler gate.
No KLD build, QEMU, smoke, or feature test was run for this batch.
+10
View File
@@ -0,0 +1,10 @@
# Pre13 L-D
The LZ4 two-byte little-endian offset now uses FreeBSD's unaligned-safe
`le16dec()` helper. Boundary checks, input advancement, zero/back-reference
validation, partial decoding, padding validation, and error returns are
unchanged.
An exhaustive 65,536-value comparison proved `ip[0] | (ip[1] << 8)` and
`le16dec(ip)` equivalent for every possible encoded offset. The final FreeBSD
KLD and LZ4 smoke gates remain required later; this phase does not run them.
+19
View File
@@ -0,0 +1,19 @@
# Pre13 L-E
The BSD-only miscellaneous `erofs_defs.h` has been removed. Its three owners
now keep their constants in the files responsible for those values:
- `super.c` owns the named private CRC32C seed.
- `dir.c` uses `sizeof(struct erofs_dirent)` directly.
- `decompressor_lz4.c` owns its private LZ4 format constants.
The former header had exactly these three production source consumers. All
constant values and use-site expressions remain equivalent. Two already
retired userspace decompression test files still referenced the removed header;
they were deleted because they also depended on obsolete userspace ABIs and did
not compile independently. No current source or test entry retains the old
include. The LZ4 macro names remain private to `decompressor_lz4.c`; the former
shared CRC and dirent macro names have no current consumers. The current
architecture file was updated; historical reports were not changed.
No KLD build, QEMU, smoke, or feature test was run for this batch.
+46
View File
@@ -0,0 +1,46 @@
# Pre13 Stage0 Gate Decisions
## Scope and Baseline
- DUT repository baseline: `9028b92343ec7f5bbc1b07477b8d4a669c436955`.
- Clean tests-dev baseline: `ce03c283e6e2cb8f1b69c8eff21f7d06098a4fbe`.
- Stage0 was a planning gate. Infrastructure fixes made in tests-dev are not DUT changes.
- The complete Stage0 runner result is `FAIL`, not `PASS`, because the H01 DTrace probe failed.
## Gate A: ROOT-NONDIR
Status: `READY`.
The baseline incorrectly mounts an image whose root NID points to a regular file. Pre13 freezes the expected rejection errno as `EINTEGRITY`. H04 may proceed with the dedicated fixture and its required targeted validation.
## Gate B: ZERO-RUN
Status: `BLOCKED/STOP`.
The analysis covered 1,360 legal states and found no zero-progress state. H07 must be recorded as a no-op and must not be implemented in Pre13.
## Gate C: Map Tuple Oracle
Status: `BLOCKED/STOP`.
The oracle reliably covers only 26 plain, inline, and hole tuples. It does not cover chunk, multidevice, bounds, or overflow behavior. H01a, H01b, and H01c must not be implemented in Pre13.
## Gate D: Comparator
Status: `READY`.
The Python comparator corpus passed all 11,562 cases. The independent C harness passed all 1,048,576 cases. H03 may proceed within the planned boundaries and with its targeted validation.
## Gate E: Device Options
Status: incomplete and non-gating.
The `device.N` runtime probe was not completed. Pre13 makes no runtime behavior claim from this gate.
## Integrity and Cleanup
- Stage0 must not be described as an overall pass.
- Stage0-owned QEMU, SSH, ports, overlays, and temporary runtime resources were cleaned up.
- The guard VM and read-only base image were not modified.
- The pre-existing dirty tests-dev working tree was unchanged.
- Tests-dev runner and infrastructure repairs are evidence support only; they are not EROFS DUT fixes.
+40
View File
@@ -0,0 +1,40 @@
# P15-005 Stage0 Decision
Status: `GO` for B06.
The authoritative baseline replay used commit
`edf098905a34de764185e72fc7e92d7b8f4e7285`. The gate extracts and compiles
the current FreeBSD `data.c` and `zmap.c` producer bodies instead of replaying
an arithmetic model. Fourteen frozen cases cover plain, inline, hole, chunk,
48-bit multidevice, compressed explicit extents, fragment, partial-reference,
post-EOF, bounds, overflow, short-read, and post-acquire error behavior. Every
field in `(m_la,m_pa,m_llen,m_plen,m_deviceid,m_flags,errno,acquire_count,
release_count)` matched the independently frozen record oracle.
The ownership oracle freezes 27 audited function bodies and 22 success,
validation-error, provider-error/short-read, transfer, and release paths across
plain data, compressed data/config, xattr, inode, superblock, and map consumers.
It also pins the corresponding Linux source hashes and `erofs_buf` acquire/put
semantic anchors. Candidate replay must satisfy the predeclared per-function
object/put/raw-buffer contract and execute the actual FreeBSD helper through
primary-image, metabox, error, overflow, idempotent-put, and null-callback paths.
The supplied prep remains correctly classified `BLOCKED`: its static scan uses
obsolete `erofs_mount`/`erofs_node` text anchors and its Python map model does
not execute DUT code. `P15-005.sh` and `P15-005-input.json` close those gaps on
the current BASE. Evidence is in
`planning/pre15/evidence/20260814T145546Z-G02-P15-005/`.
QEMU and the full feature suite were not run. G02 for P15-005 is a host/source
gate, and its candidate contract does not require either. This GO authorizes
B06 only; it does not authorize P15-006 or any B07 map-object change.
## B06 completion
B06 commit `1b1f904a674c21eed3ee29ca7df93c44429ac8d5` replayed the same
corpus and oracle with `object_mode=true`, `oracle_equal=true`, 14/14 tuples,
22/22 ownership paths, and five actual helper lifecycle paths. D, the B06 host
case, and both FreeBSD KLD configurations pass. Seven frozen FreeBSD raw I/O
and GEOM lifecycle functions are unchanged, and the pre-B07 map object hash is
unchanged. This completes the B06 dependency and unlocks B07a for its own
separate gated batch; it does not pre-approve a B07 implementation.
+60
View File
@@ -0,0 +1,60 @@
# P15-006 Stage0 Decision
Status: `GO`; B07a is complete.
The authoritative baseline replay used commit
`6673f51152a5195a8a8903aa801f820abce7936e`. This decision is independent of
the earlier P15-005 GO: P15-006 has its own corpus, model digest, source
extractors, byte encoding, and decision record.
The frozen corpus contains 80 map tuples and 13 device-resolution cases. The
map tuples comprise 50 plain/inline/chunk producer cases and 30 compressed
explicit-extent cases. They cover plain and inline boundaries, holes, raw
32-bit chunks, indexed 32-bit chunks, indexed 48-bit chunks, masked and
nonzero device IDs, all four explicit extent record sizes, fragments, partial
references, exact and post EOF, provider/metabox bounds, checked arithmetic
overflow, invalid formats and algorithms, and errors both before and after
metadata acquisition. The device cases separately exercise primary,
multidevice, flat-device, unified-address, missing-provider, range, and
overflow behavior without changing GEOM ownership.
Expected records are produced by an independent arithmetic and on-disk record
decoder whose frozen SHA256 is
`effcf0b6cc6a6e916eae89a14ad52d7b96273a453556647b6eabf2926fcb4abb`.
The gate separately extracts the real `data.c` and `zmap.c` producer bodies
from the frozen BASE, compiles them with `-Werror`, and compares every
`m_la`, `m_pa`, `m_llen`, `m_plen`, `m_deviceid`, `m_flags`,
`m_algorithmformat`, positive errno, acquire count, and release count. It
also packs those fields with fixed layout `<QQQQH2xIiiII`; the baseline tuple
bytes have SHA256
`8d3fec7ccbdfb7f7c851a494b6ecad02b8a019247bd9486188f8d37c169faedd`.
Two complete baseline replays produced byte-identical tuple, device, and
result files.
Protected hashes freeze the existing plain/chunk and compressed producers,
metadata acquire/put paths, physical I/O path, and device/GEOM mapping bodies.
The candidate replay additionally requires FULL and COMPACT dispatch to pass
all five map flag bits and all tuple fields through the new adapter. Every
observed acquisition is balanced: the baseline contains 41 paths at 0:0, 30
at 1:1, seven at 2:2, and two at 3:3. All successful non-EOF mappings have a
positive `m_llen`; Pre13 H07 remains closed.
This closes the specific Pre13 H01 oracle gaps for chunk, multidevice, bounds,
and overflow rather than reusing the old 26 plain/inline/hole tuples. The
supplied G02 prep remains a non-authoritative prototype and was not used as
the tuple source.
The B07a implementation is commit
`141b11f0d847a63a47b6c6143e4c2913a1147a0f`. Its authoritative host run
replayed all 80 tuples and 13 device cases against the frozen oracle. The
4,480-byte tuple streams are byte-identical with SHA256
`8d3fec7ccbdfb7f7c851a494b6ecad02b8a019247bd9486188f8d37c169faedd`;
FULL and COMPACT dispatch preserve every tuple field and all five map flag
bits. D and both K2 configurations pass, with no global or undefined symbol
delta from B06.
QEMU and the full feature suite were not run. The final plan requires host
tuple replay, D, and K2 for B07a; it does not require a B07a QEMU fixture.
B07b is now dependency-unblocked for a separate batch. B07c remains blocked
on B07b, and B08 remains blocked on B07c. This decision neither implements
nor pre-accepts any of those later batches.
+102
View File
@@ -0,0 +1,102 @@
# P15-019 Stage0 Decision
Status: `GO`. G03 authorizes B16 for P15-019 only. P15-046 remains the B01
test-only legacy xattr prefix fallback contract; it neither changes this
decision nor authorizes any exact-header or xattr behavior change.
The decision is bound to DUT BASE
`d645feb720c7022d2138d2a62eb72c022eb75351`, FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, erofs-utils source HEAD
`7db78788b000999e2de88decd2ba90654f26171c`, and
`mkfs.erofs (erofs-utils) 1.8.6`.
## Authoritative Run
The gate ran from `/work/erofs-freebsd-pre/repo-pre-15` as:
```sh
umask 077
timeout -k 10 240 tests/pre15/gates/P15-019.sh \
--base d645feb720c7022d2138d2a62eb72c022eb75351 \
--output /work/erofs-freebsd-pre-evidence/pre15/\
20260815T093800Z-G03-P15-019-umask-fix/gate-output
```
It returned zero with `GO`. The authoritative hashes are:
- gate script: `afa20cbd28ca9dfc8064596a312248756ed7cde54398efb5ef713818cc04ea54`
- gate input: `291bf12838981ef650e16bbf7381eaa9b2921de957e97dec46e6a573750a19b8`
- result: `bb52e1a88cb09554a51432ec545ed98d5509a529832141ed0ce83b0c336be5bb`
- cases: `0c12cdce8b3ed37e85f349af0715a728d4acabbb758451fb65546dd9836066b5`
- fixture manifest/set: `5e99e5ad9112508991e78ae6e65928973d6b0da78285a059894bed06010cf373`
- semantics: `090e6b0cd5eaa197f17da12daf29f30bd8f337d3d4ae2f0f4f5819ad41e906f9`
- cleanup: `8e3b52610ee1492262494d00c48bf66ea04d313ab9ac1ef27565fa0e607b9a97`
An additional independent replay from `umask 077` produced the same result,
cases, fixture manifest, semantics, and cleanup hashes byte for byte. The gate
sets `umask 022` internally because erofs-utils records the packed fragment
inode's source mode; this makes the fixture set independent of its caller.
## Real EROFS Fixture Oracle
The gate creates 35 deterministic EROFS images and seven external chunk blobs
in owned output. Nothing is committed as an image, blob, binary, or KLD. Each
of the five required storage paths has these seven cases:
- normal short target;
- normal 1,024-byte `MAXPATHLEN` target;
- zero-byte target;
- embedded NUL at the first, middle, and last byte;
- 1,025-byte over-limit target.
Inline positives are native mkfs symlinks. Plain, chunk, compact compressed
ztailpacking, and whole-file fragment cases start as real mkfs regular-file
carriers because the host cannot create a symlink containing NUL and mkfs does
not select those layouts for symlinks. The transform changes only inode mode,
the root dirent type, and, for empty cases, inode size, then recomputes the real
EROFS checksum. Chunk data remains on a real `--blobdev`; fragment data remains
in the real packed inode. Changed-byte offsets are recorded for every case.
The independent Python parser reads the superblock, checksum span, inode,
dirent, layout, and target bytes directly. It resolves chunk indexes and the
external blob, decodes compact inline-pcluster raw LZ4 without a DUT helper,
and resolves the whole-fragment offset through the packed inode. It does not
compile, call, or inject values into any DUT internal function. `dump.erofs`
is only a layout/type cross-check. All legal targets also pass
`fsck.erofs --extract` and reproduce the exact source bytes.
The independent decision model returns positive FreeBSD `EINTEGRITY` (97) for
empty or embedded-NUL targets, positive `ENAMETOOLONG` (63) before target I/O
for size greater than `MAXPATHLEN`, and success for exact non-NUL bytes. No
trailing NUL is required. All 35 expected results match, so every layout has a
bounded positive and negative oracle as required by G03.
## Linux and FreeBSD Semantics
Linux `erofs_fill_symlink()` validates only flat-inline fast symlinks while
building `i_link`; it uses `kmemdup_nul()`/`strlen()` and returns negative
`-EFSCORRUPTED`. Non-inline Linux symlinks use `page_get_link`. That page/cache
shape is not a FreeBSD implementation contract.
FreeBSD enters through `VOP_READLINK` on a vnode and currently streams through
`erofs_readlink_target`, the common map/read path, GEOM or the compressed
backing path, and `uiomove`. It has no Linux `i_link` or page-get-link path and
uses positive errno. B16 must therefore validate the complete immutable target
before any `uiomove`, use the existing layout-specific map/read machinery, and
keep the scan bounded by `MAXPATHLEN=1024`.
The 1,024-byte case is a successful `readlink(2)` byte target. Pathname follow
may still return `ENAMETOOLONG` when that target plus the remaining pathname
exceeds FreeBSD's namei buffer; that VFS result is distinct from the on-disk
target validator. The 1,025-byte inode is rejected by B16 before target I/O.
## Scope and Cleanup
The gate did not modify `src/**`, build a KLD, start QEMU, or run a feature
suite. It observed but did not signal or otherwise alter protected PID 26318,
port 9222, or `/work/debug-qemu/local/vm-freebsd-build.qcow2.bp`; their recorded
identity was unchanged before and after the run. Owned temporary source and
extract directories were removed, with zero leftovers.
B16 may now modify only its planned source/test write set and must pass the
specified host TC166, D, zstdio0 build, and targeted TC166 QEMU acceptance.
+93
View File
@@ -0,0 +1,93 @@
# P15-021 Stage0 Decision
Status: `GO`. B19b source is authorized; B19a remains `STOP-NO-SOURCE`.
P15-021 is the xattr Bloom fast-negative candidate. The gate uses a real,
byte-reproducible EROFS image generated by `mkfs.erofs` 1.8.6, an independent
on-disk parser, a temporary file-local `erofs_xxh32` prototype, frozen Linux
format/use-site anchors, and five cold provider-metadata-read samples. It does
not link a prototype into the DUT KLD and does not modify `src/**`.
The required matrix is hit, proven miss, collision false positive, unknown
filter format, feature off, malformed shared-count metadata, malformed shared
ID metadata, positive FreeBSD errno, 64-worker replay, one million generated
names without a false negative, and owned cleanup. Unknown formats and feature
off must scan. A collision must scan. Structural corruption reached before or
during the scan must remain `EINTEGRITY` rather than becoming `ENOATTR`.
The gate command is:
```sh
timeout -k 10 240 tests/pre15/gates/P15-021.sh \
--base 666e52f710363df07f7c93919eb835d41092d011 \
--output OUTPUT
```
## Frozen Identity
The gate BASE is `666e52f710363df07f7c93919eb835d41092d011`.
`P15-021-input.json` freezes the three DUT source files, Linux `erofs_fs.h` and
`xattr.c`, FreeBSD 15 source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, erofs-utils HEAD
`7db78788b000999e2de88decd2ba90654f26171c`, host tools, format constants,
thresholds, seven endian/seed vectors, one million generated names, 64 workers,
and the exact gate/source write sets. FreeBSD has no public xxh32 API; its only
copies are private Zstd/OpenZFS sources, so the authorized implementation is
file-local and namespaced as `erofs_xxh32`.
The generated prototype source SHA256 is
`3fe1a7a64ce4386d74f5f49455fe8bef2a2ee55a03eb72593a89d4dec70c1b4b`.
Its temporary host binary SHA256 is
`9910baed4d018a0257f38377bb48857a3205ee42d9f0ff279db71e63e5ba695b`;
the binary is evidence only, is not committed, and is never linked into the DUT
KLD.
## Fixture And Oracle
`mkfs.erofs` 1.8.6 generated the filter-bearing image twice from 64 peers and
one target with eight shared user xattrs. The two images were byte-identical;
the valid fixture SHA256 is
`b06b7c4c30adb3684d11505edf815dc0a395c03f6a95120b3150361008a182cb`.
The independent parser handles multi-block directories and separately decodes
inode xattr headers, inline entries, shared IDs, and shared entries. Legal
valid, unknown-reserved, and feature-off images pass `fsck.erofs`.
The seven-case matrix passes: present hit, proven miss, collision false
positive, unknown filter, feature off, malformed shared count, and out-of-range
shared ID. Miss alone takes the fast-negative. Hit and collision perform the
complete scan; unknown format and feature off perform the unchanged complete
scan. Corrupt header/shared metadata returns positive FreeBSD `EINTEGRITY=97`,
while valid absence returns positive `ENOATTR=87`.
The candidate implementation, independent Python xxh32, system libxxhash, and
the frozen Linux seed/endianness vectors agree. One million deterministic random
names across EROFS indexes 1/2/3/4/6 produce no candidate/oracle difference and
no false negative in 1,024 constructed valid filters. Sixty-four workers
complete 1,280 mixed lookups with identical bytes/errno.
## Benefit And Decision
Five cold samples of 200 real-fixture misses each are stable. Baseline metadata
read calls are `3400,3400,3400,3400,3400`; candidate calls are
`400,400,400,400,400`, an 88.235 percent reduction. Baseline cold unique
provider blocks are `400,400,400,400,400`; candidate blocks are
`200,200,200,200,200`, a 50 percent reduction. Both exceed the mandatory 25
percent threshold. The model counts the extra header/shared-ID reads on
positive and collision paths and therefore does not hide their overhead.
G03 is `GO` for P15-021. Only a proven negative may bypass the full scan; all
other outcomes retain the current FreeBSD extattr namespace, VOP transfer,
positive errno, metadata buffer ownership, and lock-free immutable lookup
behavior. Linux supplies the format/hash comparison, not FreeBSD vnode/cache or
locking semantics.
Attempt 1 stopped at the fixed-vector check because five hexadecimal hashes
were transcribed to incorrect decimal JSON values. Python and libxxhash agreed
on the hexadecimal values and bits; the input-only correction was replayed
under a new output directory. Attempts 2 and 3 then produced identical semantic
results and fixture hashes. Attempt 3 evidence is retained at
`/work/pre15-evidence/20260815T-P15-021-G03-attempt-3`; its `SHA256SUMS` digest
is `b81fe1b94048dbc3d17c62c95aba6a5de9e068e2a15639ca55ea60fe5a251e07`.
No QEMU or full feature suite was run for the Stage0 decision. Source remained
unchanged, owned temporary files/processes are zero, and protected PID 26318,
port 9222, and the base bp were untouched.
+87
View File
@@ -0,0 +1,87 @@
# P15-022 Stage0 Decision
Status: `GO`. B19a source is authorized.
P15-022 is the G03/G05 xattr-cache candidate. The old decision was not treated
as terminal because its multi-block path parser stopped before producing a
P15-022 result and G05 had zero samples. The repaired gate completed both
mandatory branches before source implementation.
## Frozen Identity
The replay base is `50a4e84d0da33592a81361e0294b7feb5bbd3ffa`.
`P15-022-input.json` freezes the current DUT/Linux source, exact-ABI FreeBSD
source `/work/dev-freebsd-releng` at
`106727738dcfb6c001b46f25363b91cece970085`, host tools, B17 assets, B19a
generator/oracle/model assets, a 1 MiB mount budget, a 64 KiB per-vnode body
limit, 64 workers, five samples per variant, and the unchanged 25 percent
provider-metadata-read threshold.
## Oracle Repair And G03
The B19a oracle now reads every logical directory block, including a short
final block, and validates name offsets, embedded NUL rules, strict in-block
ordering, strict adjacent-block ordering, stable entry order, duplicate
resolution, and missing-path classification. Its self-controls cover target
lookup after a block boundary, duplicate within/across blocks, duplicate
resolution, missing target, backwards boundary, and an invalid name range.
The real fixture retains 64 long-named peers and one target. It has 2 root
directory blocks, 68 entries, and resolves `target.bin` in block 1 after the
`peer-047`/`peer-048` boundary. The target contains both one inline xattr and
one shared xattr. Valid, corrupt shared-count, corrupt shared-ID, and corrupt
inline-name fixtures are independently checked with positive FreeBSD errno.
G03 completed twice on byte-reproducible images. The existing B17 oracle also
replayed twice with all 10 legal and 15 damaged images passing and frozen
fixture-set SHA256
`d821aeb36de37ae40b817b91f8169e585721c33a3a9e59098cdfbcbfff74e364`.
## G05 Result
The same image, host, operations, warmup, loops, and sample order were used for
both variants. Five raw samples per variant are retained; no failed sample was
filtered. Baseline provider metadata reads were
`1400,1400,1400,1400,1400`; candidate reads were
`800,800,800,800,800`. Median reduction is `42.857142857142854%` for both
instrumented calls and provider block reads, above the unchanged `25.0%` gate.
The focused cache model passes 64-worker one-owner publication, identical
waiter bytes and typed failure, failed initialization without a half-published
body, the 64 KiB entry limit, exact 1 MiB budget exhaustion fallback, invalidation,
inflight close, reclaim, and zero resident bytes after cleanup. The decision
is `GO`; source modification is now authorized only within the B19a write set.
## Evidence And Scope
Evidence is retained under
`planning/pre15/evidence/20260817T171814Z-B19a/`. `GATE-attempt1` preserves the
runner failure caused by the missing sample directory. `GATE-attempt2` is the
authoritative GO replay with raw fixtures, oracle reports, cache-model output,
sample JSON, aggregate TSV, commands, and cleanup manifest.
The authorized source/test write set is:
```text
repo-pre-15/src/erofs_vnops.c
repo-pre-15/src/inode.c
repo-pre-15/src/internal.h
repo-pre-15/src/xattr.c
repo-pre-15/tests/pre15/cases/B19a-xattr-cache.sh
repo-pre-15/tests/pre15/fixtures/B19a-*
```
No QEMU or production source was touched by this Stage0 gate. B36 and all
other execution units remain out of scope.
## B19a Acceptance Addendum
B19a subsequently implemented the authorized per-vnode xattr body/shared-ID
cache within the declared write set. The focused FreeBSD 15.0-RELEASE-p8
exact-ABI run passed first, concurrent, and repeated xattr reads; symmetric
`EINTEGRITY` results for all three damaged images; normal and forced unmount;
cache invalidation and reclaim; KLD unload; guest mount/md cleanup; target and
runner cleanup; and an empty dmesg delta. The runner retained the original
1200-second case deadline, focused command timeouts, 64 KiB body limit, 1 MiB
mount budget, and 25 percent G05 threshold. The final evidence is under
`planning/pre15/evidence/20260817T193130Z-B19a-final/`.
+43
View File
@@ -0,0 +1,43 @@
# P15-027 Stage0 Decision
Status: `STOP`. B36 is `STOP-NO-SOURCE`.
G07 is evaluated per architecture against frozen BASE
`4683579dabc57b0c9aaf5762e13ecc2a0ec3f8f9`. The mandatory audit set is
arm64, riscv64, and i386. i386 is the real FreeBSD 32-bit target: the
available FreeBSD source contains its machine headers and the pinned Clang
toolchain contains the x86 backend. Source/toolchain availability is not
native runtime evidence.
The inventory-first gate finds no declared same-architecture FreeBSD runtime
for arm64, riscv64, or i386. The host is Linux/x86_64. An emulator binary,
x86_64 guest, Linux execution, cross compilation, static layout output, or a
runtime from another candidate cannot satisfy this condition. Missing native
runtime is a per-architecture `STOP`, not `INFRA_BLOCKED`, and no architecture
can borrow another architecture's result.
Because cross-only results cannot change any current decision, dual
`WITH_ZSTDIO=0/1` KMOD builds and layout/unaligned/endian probes are
`NOT_RUN` for all three architectures. The gate still verifies the frozen
EROFS source tree, selected planning and probe hashes, FreeBSD source identity
and architecture headers, compiler identity and target backends, and exact
input-recorded Makefile relaxation. That patch is applied only to an owned
temporary Makefile copy. The production `src/Makefile` remains byte-identical
to BASE and retains the amd64-only error.
For a future rerun, each architecture has an independent native runner slot.
A declaration must pass a bounded preflight reporting matching FreeBSD
`uname -s`, architecture-specific `uname -m` and `uname -p`, and cleanup.
Only then does the gate create a frozen temporary source copy, apply the sole
recorded Makefile patch, build both KMOD configurations, record sizes and
`nm -u`, and compile the layout and unaligned little-endian probes. The same
runner must then load and unload both exact module hashes and record Plain,
LZ4, LZMA, and Zstd reads. It must also prove running-kernel `options ZSTDIO`
capability and exact disabled `EOPNOTSUPP`. Every operation carries argv,
deadline, exit, target marker, cleanup, stdout/stderr, and hashes. A candidate
is GO only when its own cross and native records both pass.
This decision does not authorize any B36 allowlist entry. No production
source, B36 case, B36 probe, QEMU run, K build, feature suite, or smoke suite
is created or run. Protected PID 26318, port 9222, and the shared base image
are outside the gate and are not addressed.
+51
View File
@@ -0,0 +1,51 @@
# P15-030 Stage0 Decision
Status: `STOP`. B37a is `STOP-NO-SOURCE`.
G08 is replayed against frozen BASE
`3e1d26a53eef30ecadc3407444d214feac861cb4`. P15-030 may proceed only if all
four conditions pass: an existing diagnostic or operational consumer, bounded
atomic counters, a FreeBSD 15 native `sysctl_ctx` lifecycle that closes every
required teardown path, and a stable privileged-read ABI that leaks no
unauthenticated metadata.
The consumer condition is evaluated first because no prototype can create its
own justification. The bounded inventory covers all 3,206 tracked paths under
`repo-pre-15` and `planning/pre15` at BASE, without reading
`planning/pre15/introduction.md` or treating this addendum as evidence. It
finds 54 broad sysctl/sysfs references: 31 planning or audit records, one
historical evidence record, 19 test-only paths, and three project reports or
documents. There are zero production or operations paths and zero qualifying
consumer declarations.
A qualifying future declaration must use schema
`pre15-p15-030-consumer-v1`, identify a versioned diagnostic or operational
consumer in production use, name its owner, workflow, deployment reference,
privileged-read access mode, and consumed signals, and bind a tracked non-test
implementation by path and SHA-256. The implementation must actually invoke
sysctl and reference each declared signal. A hypothetical future user, Linux
sysfs analogy, generic observability value, test script, or gate prototype is
rejected. If a declaration appears, this STOP-only run refuses false GO and
requires the FreeBSD 15 native lifecycle prototype before source authorization.
The first condition is `STOP`, so the bounded atomic-counter prototype,
FreeBSD 15 parse-failure/normal-unmount/forced-unmount/delayed-handler/
`sysctl_ctx_free`-failure lifecycle work, and permissions/ABI/leak probes are
all `NOT_RUN`. No host model is reported as native evidence and no prototype,
KLD, QEMU process, feature test, or smoke test is built or run.
P15-068 and B37b also close as `STOP` / `STOP-NO-SOURCE` because the plan
explicitly requires the P15-030 transport. This is capability dependency
closure, not an independent decision about UUID or volume-label formatting.
The identity oracle is `NOT_RUN`; no P15-068 gate addendum and no second sysctl
lifecycle are created.
The committed replay evidence is recorded under
`planning/pre15/evidence/20260816T041649Z-G08-P15-030/`. The gate owns one
temporary directory, uses a 60-second internal timeout plus the recorded outer
timeout, records argv, scope/source/addendum hashes, result, and cleanup, and
rejects an existing output directory.
No production source, B37a/B37b case, fixture, or empty commit is created.
Protected PID 26318, port 9222, and the immutable base image are outside the
gate and are not addressed or hashed.
+45
View File
@@ -0,0 +1,45 @@
# P15-031 Stage0 Decision
Status: `STOP`. B38 is `STOP-NO-SOURCE`.
G08 is replayed against frozen BASE
`0f696ad1e2c6628022d02ce52d07eb66704769dd`. P15-031 may proceed only if all
five conditions pass: an existing project diagnostic consumer that has
actually captured the complete predeclared mount/map/vget/xattr/decode/cache
schema through FreeBSD DTrace/SDT or a justified KTR path; no pointer,
credential, or unauthenticated metadata leakage; an owned-temp FreeBSD 15
prototype with exact fields/counts and closed failure/detach/unload lifecycle;
disabled `WITH_ZSTDIO=0/1` builds with no new undefined symbols; and a 10-run
hot-path median regression of at most 3% with retained interleaved controls.
The consumer condition is evaluated first because neither a gate prototype nor
a proposed B38 test can create its own project need. The bounded inventory
covers all 3,243 tracked paths under `repo-pre-15` and `planning/pre15` at
BASE, without reading `planning/pre15/introduction.md` or treating this
addendum as evidence. It finds 33 native-tracing references: 16 planning or
audit records, three historical evidence files, 10 test-only paths, and four
reports or documents. There are zero production/operations paths, zero
consumer manifests, and zero qualifying consumers.
A future declaration uses schema `pre15-p15-031-consumer-v1` under
`repo-pre-15/diagnostics/`. It binds a versioned production implementation and
actual FreeBSD 15 capture by path and SHA-256, identifies owner/workflow/
deployment, records privileged access and DTrace/SDT or justified KTR
transport, binds the complete event-schema hash, and records a positive count
for every event. The gate rejects a hypothetical maintainer, Linux
tracepoints, generic usefulness, a proposed test, its own prototype, or merely
installed `dtrace`. If an approved declaration appears, this STOP-only version
refuses false GO and requires the remaining native prototype, privacy, build,
and benchmark stages before source authorization.
The first condition is `STOP`, so privacy execution, prototype generation,
`WITH_ZSTDIO=0/1` builds, undefined-symbol comparison, the 10-run benchmark,
D/H/K0/K1, targeted QEMU TC178, full feature, and smoke are all `NOT_RUN`.
The gate's seven decision controls prove that GO is emitted only when all five
conditions are `PASS`; no benchmark samples or later-stage metrics are
fabricated.
No `src/erofs_trace.h`, trace callsite, `src/Makefile` change,
`tests/pre15/cases/B38-trace.sh`, source commit, or empty commit is created.
Protected PID 26318, port 9222, and the immutable base image are outside this
STOP path and are not addressed or hashed.
+106
View File
@@ -0,0 +1,106 @@
# P15-032 / G09 vnode-backed image gate
## Decision rule
This gate is source-preparatory only. It does not link its model into EROFS and
does not authorize B39 merely because a declared state sequence looks safe. It
pins and extracts the relevant FreeBSD 15 vnode, pager, mount, GEOM, md(4),
nullfs, unionfs, tarfs, and deadfs contracts, compiles an owned-temporary C
lifecycle model generated from `P15-032-input.json`, and rejects adversarial
false-GO mutations.
The runner exits with:
* `0`: gate GO;
* `10`: gate STOP;
* `20` or another nonzero value: runner failure;
* `124`: absolute gate timeout.
All phases have absolute deadlines. The generated C source and its output are
copied into the requested evidence directory; the temporary executable is
removed by the runner trap.
## Closeable local vnode contract
The pinned FreeBSD sources support a coherent local lifecycle:
1. Use an explicit `vnode:/absolute/path` tag; GEOM remains explicit or legacy
GEOM syntax. A mount and all `device.N` sources use one backend kind.
2. Resolve with `namei`, require `VREG`, retain vnode identity with
`vn_open_vnode(FREAD)`, retain the mounter credential with `crhold`, and use
that credential for every source `VOP_READ`.
3. Apply `VOP_SET_TEXT` before first I/O. The default write-count contract
rejects existing or later writers with `ETXTBSY`; retain a size snapshot and
convert a short source read into `EIO`. Rename or pathname replacement does
not change the held vnode identity.
4. Read synchronously into `UIO_SYSSPACE` under a source range lock and source
vnode lock. FreeBSD's old vnode-pager fallback drops the VM object write
lock before `VOP_READ`, so no user-buffer pager fault is introduced by this
read shape.
5. Register the source mount with `vfs_register_upper_from_vp` before first I/O.
On unmount, reject new I/O, drain delayed and in-flight completions, `vflush`,
unregister the upper mount, unset text, close the vnode, release the held
credential, and then release the final mount-private reference. A forced
dead source returns `ENXIO` rather than silently changing identity.
6. Keep GEOM open/read/close and vnode open/read/close as disjoint tagged-union
branches. This preserves the existing GEOM and multidevice behavior.
The generated model checks this order and independently removes each local
invariant to ensure that every mutation is rejected.
## Blocking FreeBSD contract
The required pre-I/O self and ancestor check is transitive across VFS and GEOM,
not only across pathname aliases:
```
regular source vnode
-> source filesystem
-> md(4) GEOM provider
-> md_s.s_vnode.vnode
-> another filesystem vnode
```
The final identity edge is not available through a generic, identity-preserving
FreeBSD API:
* `VOP_GETLOWVNODE` exposes vnode-stack aliases such as nullfs and unionfs, but
it does not traverse a filesystem's GEOM storage dependency.
* `vfs_register_upper_from_vp` pins the immediate source vnode mount and orders
its unmount, but it does not register hidden GEOM-to-vnode backing edges.
* `md(4)` stores the held backing vnode in the private `struct md_s` defined in
`md.c`. Its GEOM object exposes only `void *softc`; dump configuration exposes
a pathname, not a held vnode identity.
* Re-resolving that pathname fails the rename/replace invariant. Casting
`g_geom.softc` to a copied private `struct md_s` is an undocumented,
class-specific dependency and does not cover other filesystem-private or
GEOM-private file-backed providers.
Consequently a visible-only oracle can approve all vnode, credential, pager,
resize, and unmount checks while missing the hidden backing-vnode ancestor. The
gate includes that case as an adversarial false-GO control. There is no sound
place to return the required single cycle errno (`EDEADLK`) because the cycle
identity cannot first be discovered.
## Result
`P15-032` is **STOP** and B39 is **STOP-NO-SOURCE**. Recursive I/O and its lock
graph cannot be statically excluded for the requested regular-file source
surface using documented generic FreeBSD interfaces. A safe future GO needs a
new kernel dependency API that returns and pins transitive backing vnode
identities, or an explicitly narrower feature contract whose permitted source
filesystems have no hidden storage dependencies. Neither change is in B39's
authorized write set.
Run from the repository root:
```sh
repo-pre-15/tests/pre15/gates/P15-032.sh \
--base bd5a09054e5cf89efd4db82aadb051f20b06ebf7 \
--freebsd-src /work/build/freebsd-src \
--output /absolute/owned/output/path
```
No EROFS source, Makefile, feature documentation, B39 case, or B39 fixture is
modified by this STOP addendum. D, H, K, Q, TC006, TC179, TC184, smoke, and the
full feature suite are not run at the gate stage.
+85
View File
@@ -0,0 +1,85 @@
# P15-038 Stage0 Decision
Status: `GO`. B33 is authorized.
The authoritative frozen-BASE run is
`planning/pre15/evidence/20260816T021736Z-G05-P15-038/` against
`5d6755649a369498a9b257bb2d1d1e3496d5135e`. Its state model, hard-budget
accounting, real codec oracle, eviction check, and owned cleanup all pass.
The gate compares the current LZMA-only admission policy with a test-only,
codec-neutral single-entry decoded cache. Both variants execute the same 1,024
deterministic random 4 KiB logical reads against the same 256 KiB payload for
each of LZ4, LZMA, Deflate, and Zstd. Each variant has exactly five cold-cache
samples; no sample may be discarded. The helper uses the frozen host
liblz4/liblzma/zlib/libzstd libraries and performs real compression and full
decode work. It contains no synthetic sleep.
This is an independent host codec-cost oracle. It can prove avoided codec work
and byte correctness, but it is not guest vnode latency evidence and must not
be reported as such. Provider I/O, map lookup, VOP dispatch, and guest kernel
scheduling are outside its scope.
The state model preserves B32's exact key, one-owner, waiter, typed-failure,
retry, and no-cache fallback rules. It additionally freezes mount-cache then
global-budget lock order, reservation-before-decode accounting, codec-neutral
work/size admission, disabled policy, global exhaustion, ready eviction,
inflight reclaim refusal, successful reclaim, unmount drain, and key reuse.
Reserved plus resident decoded bytes are charged to the same hard budget.
G05 is GO only if the state model closes, all current/candidate hashes match,
eviction-before/after SHA-256 matches, every candidate sample stays within the
fixed budget, and at least two codecs improve median latency by 10 percent or
more. Any missing sample, correctness mismatch, lifecycle gap, or budget
overflow is STOP for P15-038 only.
## Result
All five current and five candidate samples are retained for each codec. The
median current/candidate latencies and improvements are:
| Codec | Current median | Candidate median | Improvement |
|---|---:|---:|---:|
| LZ4 | 44,988,337 ns | 6,583,335 ns | 85.367% |
| LZMA | 7,278,779 ns | 7,286,634 ns | -0.108% |
| Deflate | 186,791,959 ns | 6,102,297 ns | 96.733% |
| Zstd | 20,355,035 ns | 6,236,635 ns | 69.361% |
Three codecs exceed the required 10 percent threshold. LZMA is the expected
control because both current and candidate policies admit it; its negative
0.108 percent delta is retained and is not excluded or rewritten.
Every candidate sample charges exactly 262,144 resident bytes against the
262,144-byte mount budget. The state model reaches the 524,288-byte global
limit with two mounts, proves a third mount's synchronous no-cache fallback,
reclaims one reservation, retries successfully, and returns to zero bytes.
It also passes 16-thread success and typed-failure waves, ready eviction,
inflight reclaim refusal, key reuse, policy disable, low-work/oversize bypass,
and unmount drain. The real Deflate decode before and after eviction has
SHA-256 `5d3c21088380524a78c52b067d47a93117c5385464c5dfc83e8de598119511bb`.
No QEMU process was started by this pre-source gate. No guest vnode latency is
claimed. No production source, B32 correctness path, B32 QEMU runner, protected
PID 26318, port 9222, or shared base image was touched. B33 may now execute its
exact write set and acceptance matrix.
## B33 Execution
B33 source/test commit `bc56f830918b76029871b60cca2e53992de70a2e`
implements codec-neutral decoded-work/size admission with fixed hard mount and
global budgets, loader configuration/disable controls, four-codec accounting,
FreeBSD `vm_lowmem` reclaim, eviction, unmount release, and synchronous
no-cache fallback. It changes only `src/internal.h` and `src/zdata.c` in the
production tree and preserves the B32 key/inflight/failure contract.
D, authoritative H `20260816T025825Z-host-B33-cache-policy-1393732-0`, and
FreeBSD 15 cross-KLD builds with zstdio0 and zstdio1 pass. The final H-tested
seven-path source/test tree is byte-identical to the committed tree. Q
TC168-cache-inflight is `INFRA_BLOCKED`: guest SSH was not ready at the exact
absolute 300-second deadline, so no guest source, case target, KLD load, vnode
latency, or guest PASS is claimed. Owned PID 1389807, overlay, and port 33537
were cleaned; the protected process, port, and base metadata are unchanged.
The Q attempt predates the final replacement-order and policy-bypass-accounting
changes and did not reach the DUT. Full correspondence details and exact hashes
are in `planning/pre15/evidence/20260816T023315Z-B33/VERDICT.md`.
+44
View File
@@ -0,0 +1,44 @@
# P15-045 Stage0 Decision
Status: `STOP`. B35 is `STOP-NO-SOURCE`.
G07 is replayed against frozen BASE
`13974efc00c31d8c13c8dccb7c65a82adafcbff6`. Its first mandatory condition
is a concrete, versioned support/deployment manifest in existing project
evidence that identifies deployment targets and explicitly requires Zstd to
be enabled by default. General codec availability, prior Zstd test success,
Linux defaults, and maintainer preference are intentionally nonqualifying.
The gate inventories every tracked path in the frozen
`planning/pre15/evidence` tree (tree object
`3ecce7a124d153638853ab5d897126c769125309`, 2,560 paths), locates every text
record containing Zstd/Zstandard, and reviews records with deployment,
support, manifest, policy, default, or requirement signals. It accepts either
a structured JSON manifest or explicit text manifest headers, but requires a
manifest kind, version, nonempty deployment/support targets, and an
affirmative Zstd-default requirement together. The replay finds 596 Zstd text
records and 206 demand-signal candidates; no record meets that contract.
The missing deployment-demand prerequisite determines STOP before runtime or
build work. The disabled exact-`EOPNOTSUPP` runtime check, enabled real Zstd
EROFS read, FreeBSD kernel `ZSTDIO` symbol/capability check, and KLD size delta
measurement are therefore `NOT_RUN`; none is reported as PASS. In particular,
the gate does not claim or attempt to load a generic dependency KLD. A
false-GO guard requires every G07 condition to be PASS.
The frozen source is checked only to preserve the current policy boundary:
`src/Makefile` contains exactly one `WITH_ZSTDIO?= 0` default, the disabled
stub returns `EOPNOTSUPP`, and the documentation names kernel
`options ZSTDIO`. These are static observations, not substitutes for the
runtime conditions omitted after the prerequisite STOP.
The committed replay evidence is recorded under
`planning/pre15/evidence/20260816T033449Z-G07-P15-045/`. The gate owns one
temporary directory, uses a 90-second internal absolute timeout plus the
recorded outer timeout, records argv, source/candidate hashes, exit status,
and cleanup, and rejects an existing output directory.
No production source, B35 case, B35 fixture, QEMU process, full feature suite,
or smoke suite is run or changed. Protected PID 26318, port 9222, and the
shared base image are outside the gate. The supported policy remains opt-in
`WITH_ZSTDIO=1`, with default `WITH_ZSTDIO?=0`.
+74
View File
@@ -0,0 +1,74 @@
# P15-052 Stage0 Decision
Status: `STOP`. B20 is complete as `STOP-NO-SOURCE`; B21 was not started.
The authoritative G06 replay uses frozen BASE
`ca7bb4fe6b33e4a1bdf423801134b0ed6bda86dd`. It verifies the exact current
FreeBSD and Linux source hashes, the FreeBSD amd64 `PAGE_SHIFT=12` contract,
signed 64-bit `off_t`, and GEOM's `off_t mediasize` before evaluating every
listed branch.
## Exact Branch Result
All twelve proposed on-disk arithmetic branches have unique source sites, but
none has a target marker reachable from one validated on-disk field mutation.
The current source text returns positive `EOVERFLOW` at those defensive sites;
the proposed mapping would be positive `EINTEGRITY`. Linux has no matching
checked branches in these functions, so its relevant semantic mapping is
negative `-EFSCORRUPTED`, not a textual negative copy of the FreeBSD return.
The decisive counterexample to the supplied READY prototype is
`xattr.metadata.header_add`. The prototype directly mutates the local
`aligned_off` value. The real helper first rejects input above
`UINT64_MAX - 3`, then rounds to four bytes. The greatest surviving aligned
offset is therefore `UINT64_MAX - 3`; adding the two-byte header cannot
overflow. The named branch cannot be independently reached.
The other prototype vectors likewise inject values outside current provenance:
mounted image bytes are bounded by GEOM's signed `off_t mediasize`; inode size
is bounded by `OFF_MAX`; decoded physical blocks are at most 48 bits;
`blkszbits` is at most 12; prefix start and shared IDs are 32-bit. These bounds
prevent each proposed shift, add, alignment, and index overflow before the
listed target.
## Preserved Semantics
The replay separately freezes corruption, unsupported, provider I/O, EOF, and
short-read behavior. Disk/backing range contradictions remain positive
`EINTEGRITY`; exact zero-length EOF remains success; provider `EIO` and media
`ENXIO` remain exact; unsupported xattr layout remains positive
`EOPNOTSUPP`; allocation remains positive `ENOMEM` and outside B20. Linux
counterparts retain negative errno or `PTR_ERR` propagation.
The nominal `erofs_xattr_read_backing()` `off > INT64_MAX` positive
`EOVERFLOW` site is also not reachable for a mounted primary provider: the
preceding range check requires `off <= backing_size`, while mount validation
requires `backing_size <= INT64_MAX`. This site remains unchanged because a
STOP decision has no source diff.
## Atomic Decision
G06 requires every listed branch to be independently reachable. One missing
branch stops all of P15-052; this replay finds twelve missing target markers.
No `data.c` or `xattr.c` errno is changed, no B20 case/fixture is created, and
no candidate replay or QEMU run can cure a host-proven reachability failure.
The full feature suite was not run.
The authoritative command was:
```sh
timeout -k 10 240 tests/pre15/gates/P15-052.sh \
--base ca7bb4fe6b33e4a1bdf423801134b0ed6bda86dd \
--output OWNED_OUTPUT
```
It exited 1 because a valid gate `STOP` is not `GO`. Two fresh output
directories were byte-identical. The initial replay produced result SHA256
`37280545d8ef5a6b87c7b9d536939513a1bbac8e4e4b2389e84f5ca77e1aa522`,
branch-ledger SHA256
`8024a2cb6b43bd10fbcf446cf8cf44de37f1f9d6df25718c641cb9db4c257169`,
and preservation-ledger SHA256
`d17e05b8df43ea5173bed6e58d7431b37a7a9c4a143e07717b543efcb54ecc66`.
B21 is not authorized because the execution request requires B20 PASS before
B21. Wave16 is therefore not ready from this serial chain.
+13
View File
@@ -0,0 +1,13 @@
# P15-057 Stage0 Decision
Status: `GO`; B17 is complete.
The B17 fixture gate contains 25 reproducible EROFS images and independently
freezes all legal results and damaged positive FreeBSD errnos. The source
commit `433cf4ec66478b65b291ec5b21a0bf6d806bd14f` preserves the raw xattr filter
feature declaration, saves its reserved byte, and gates the current filter
format only at the ACL use site. Reserved values 0, 1, and 255 and feature
present/absent combinations pass host and minimal FreeBSD 15 QEMU replay.
Evidence is in `planning/pre15/evidence/20260814T203439Z-B17/`. This decision
does not authorize the separate cache, Bloom, or ordering batches.
+13
View File
@@ -0,0 +1,13 @@
# P15-058 Stage0 Decision
Status: `GO`; B17 is complete.
The B17 corpus separately covers inline names, shared names, and long-prefix
infixes containing embedded NUL, with independently frozen `EINTEGRITY` (97)
reject points. It also covers legal ACL empty-suffix names and short/long
name-index failures. Source commit
`433cf4ec66478b65b291ec5b21a0bf6d806bd14f` rejects only the length-delimited
embedded-NUL formats and retains the FreeBSD namespace and extattr ABI.
Evidence is in `planning/pre15/evidence/20260814T203439Z-B17/`. This decision
does not include xattr cache, Bloom, or call-order changes.
+89
View File
@@ -0,0 +1,89 @@
# P15-062 Stage0 Decision
Status: `STOP`. B26 is `STOP-NO-SOURCE`; no production source, B26 case, or
B26 fixture is authorized.
The decision is bound to DUT BASE
`205a90465edb64e83ba44aaacac9bedad4cbe905`, FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, and exact upstream erofs-utils
commits for v1.4, v1.7, and v1.8.6. The authoritative gate evidence and final
script/input hashes are recorded below and in the G04 evidence commit.
The later B26 terminal review is recorded at
`planning/pre15/evidence/20260817T-B26-terminal-review/VERDICT.md`.
It retains `STOP_NO_SOURCE / NOT_TESTED` and adds secondary
`TERMINAL_REVIEWED_NO_SOURCE`. It found the historical zero-tail shape to be
reproducible, but did not establish that trailing zeros are format-mandated or
that current FreeBSD rejects them.
## Authoritative Run
The gate ran from `/work/erofs-freebsd-pre` as:
```sh
timeout -k 10 240 repo-pre-15/tests/pre15/gates/P15-062.sh \
--base 205a90465edb64e83ba44aaacac9bedad4cbe905 \
--output planning/pre15/evidence/20260815T062257Z-G04-P15-062/gate-output
```
It returned the gate-defined STOP exit status 1. Evidence is rooted at
`planning/pre15/evidence/20260815T062257Z-G04-P15-062/`. The authoritative
script SHA-256 is
`6af503f1612eb42410f91a54fdc37481bae86a4419232c9de48c08bfa7ecee5d` and
the input SHA-256 is
`390202738ac006aba2514d27d3b0a19a0123a009601a26cac3b560a3dff388e5`.
The result SHA-256 is
`597466203edcaee6d163651a6d6a05e49dff24f39201234911f9753b29eebbe0`.
## Decision Rule
G04 permits P15-062 only if every normative and historical full LZ4 input uses
the same exact-consumption rule. The B26 review found that the old fsck
acceptance path uses `LZ4_decompress_safe_partial()` for legacy/no-0padding
images, so that acceptance does not prove full physical consumption or format
legality. The format sources contain no stored compressed-stream length or
general trailing-zero rule. The current FreeBSD decoder accepts an all-zero
remainder and rejects nonzero remainder bytes; no implementation defect was
demonstrated.
## Reproducible Corpus
The gate builds three upstream mkfs generations from exact commits in an owned
temporary directory. Each version twice generates the same fixed-time,
fixed-UUID, root-owned, xattr-free legacy LZ4 image from the same deterministic
1 MiB file. Matching `fsck.erofs --extract` accepts each image. The image,
source, build tree, and binaries remain temporary and are not committed.
The independent parser reconstructs the complete file from two physical
pclusters. The final extent has `m_plen=4096`, but its raw LZ4 stream reaches
9,670 decoded bytes after consuming 48 bytes; the remaining 4,048 bytes are
zero. This shape is byte-reproducible in v1.4, v1.7, and v1.8.6. The current
FreeBSD decoder accepts the whole input and liblz4 accepts the 48-byte exact
stream but rejects the block-sized input. The zero-tail shape is therefore a
historical tool-compatible candidate, not a proven format-legal fixture.
## Partial and Corruption Controls
A separate v1.8.6 `-Ededupe` image contains two real noncompact
`Z_EROFS_LI_PARTIAL_REF` records that share an LZ4 pcluster. The independent
parser, liblz4 partial API, and frozen DUT callback agree on the 4,096- and
5,594-byte prefixes. Corruption beginning after the 4,096-byte request remains
undetected by that partial read but causes the same fixture's full decode to
return positive `EINTEGRITY` (97), as required by G04. Truncation and nonzero
tail controls also return 97; all output guards remain unchanged.
Production `zdata.c` ownership anchors show that metadata/physical input is
released after every decoder return, failed decoded output is freed, and only
successful output is published. The decoder itself allocates and owns no
buffers. The gate's owned build/image directory is removed, and it does not
start QEMU or touch protected PID 26318, port 9222, or the shared base image.
## Consequence
P15-062 remains STOP for Pre15 because B26 has no authorized production source,
case, or fixture. `src/decompressor_lz4.c`,
`tests/pre15/cases/B26-lz4-input.sh`, and `tests/pre15/fixtures/B26-*` remain
unchanged or absent as applicable. B26 acceptance D, exact-ABI build, TC167
QEMU, and the full feature suite are not run because no implementation error
was demonstrated. The secondary B26 status is
`TERMINAL_REVIEWED_NO_SOURCE`.
+75
View File
@@ -0,0 +1,75 @@
# P15-076 Stage0 Decision
Status: `STOP`. B29 is `STOP-NO-SOURCE`.
The gate is bound to frozen DUT BASE
`3e9bc3f03ba9c39c38cc40f2f08eb6e769557f55` and FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`. Three host attempts are retained:
- `20260815T170841Z-G05-P15-076`: `RUNNER_FAIL`, FreeBSD headers shadowed
glibc headers during host compilation.
- `20260815T170931Z-G05-P15-076`: `RUNNER_FAIL`, the Deflate oracle compared
one selected extent with the complete source payload.
- `20260815T171051Z-G05-P15-076`: the runner returned 0, but final review
rejects its local `GO` because mandatory state-model conditions are not
closed.
## Last Attempt Command
```sh
timeout -k 10 240 repo-pre-15/tests/pre15/gates/P15-076.sh \
--base 3e9bc3f03ba9c39c38cc40f2f08eb6e769557f55 \
--output planning/pre15/evidence/20260815T171051Z-G05-P15-076/gate-output
```
The command returned 0 in three seconds. The gate script SHA-256 is
`1d2cc18de150c8bdeb4fa8d284ab345f9ec4249c2ba1b50e1badbe7f942b42dc`,
the input SHA-256 is
`30497cf86785c367dcd4a3d6889ba6a6302f6f133d68e4af31d984a1031e0058`,
and the result SHA-256 is
`ac007afc4ad5a97bb1561888105c67612d3eea5427f77c54a858bede4c86e0a3`.
## Benefit Result
The last attempt regenerates the frozen B28 EROFS fixtures in owned temporary
storage and decodes the exact selected LZMA, Deflate, and Zstd extents. Input,
expected output, and output buffers are outside the tracked allocator region.
Baseline creates and destroys a context per decode; the prototype resets one
context across 64 exact decodes. Seven samples are retained per mode.
| Codec | Baseline events | Reused events | Reduction | Measured peak |
|---|---:|---:|---:|---:|
| LZMA | 128 | 2 | 98.4375% | 28,504 B |
| Deflate | 256 | 4 | 98.4375% | 39,928 B |
| Zstd | 128 | 2 | 98.4375% | 95,992 B |
All tracked baseline backend allocator events are context lifecycle events, so
the measured share is 100 percent and passes the 10 percent threshold. All
three event reductions pass the 25 percent threshold. Exact output and
balanced allocation/free counts pass. CPU remains diagnostic: Deflate is
1.30 percent faster, LZMA is 10.20 percent slower, and Zstd is 15.35 percent
faster by median per-decode time; the LZMA regression is retained.
## STOP Review
G05 requires both the benefit threshold and a complete pre-source state model.
The retained runner does not establish a hard memory limit: its 329,616-byte
mount and 2,636,928-byte global calculations use peaks from representative
streams. In particular, its Zstd prototype sets `ZSTD_d_windowLogMax` to 16,
while the frozen DUT accepts values through 20. The measured 95,992-byte Zstd
peak therefore is not a hard bound for the production-supported range. LZMA is
also sampled with a 65,536-byte dictionary while the DUT accepts up to 8 MiB.
The state model also does not execute a global-exhaustion transition. It sets
`global_owned` and `mount_owned` to the mount limit of two and selects fresh
allocation because the mount is full; the independent global limit of 16 is
never reached. Text describing global exhaustion cannot replace that missing
assertion. Therefore the hard-cap and global-exhaustion requirements are not
closed, and the runner-local `GO` cannot authorize source work.
No threshold, sample, or result is changed to force a decision. Per G05, one
failed mandatory condition makes P15-076 `STOP`; B29 is recorded
`STOP-NO-SOURCE`. No production `src/**`, B29 case, fixture, or helper was
changed. B29 H/K/Q acceptance, QEMU, and the full feature suite were not run.
The hard-timeout D check passed. Owned temporary cleanup passed; protected PID
26318, port 9222, and the shared base image were untouched.
+95
View File
@@ -0,0 +1,95 @@
# P15-081 Stage0 Decision
Status: `GO`. B11 is authorized; no production source was modified by this
decision.
The authoritative G11 replay uses frozen B25 BASE
`e2e3fb86b6fffcb01d6fd29c17dd95628ad070de`, FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, and
`mkfs.erofs (erofs-utils) 1.8.6`. The gate script and input SHA256 values are
`6215a3005214ba8d25dacd63e039320a0c63308d2334b5faff99a3927cad7d2e`
and `a887fad56927545adb48462b770100d1e64b22b32554ed8ffad14c72b8febfc3`.
## Real Disk and Normal Entrypoint
The gate independently materializes a deterministic source tree containing
regular, directory, character-device, block-device, FIFO, socket, symlink,
and two hardlink names. It invokes mkfs twice with fixed UUID, time, ownership,
worker count, xattr policy, and inline-data policy. It also fixes and restores
the process umask and explicitly sets every source-node mode. Complete
replays under outer umask `022` and `077`, including both seed generations in
each replay, are byte-identical.
The gate parses the actual superblock, checksum range, root inode, inline root
directory, 12-byte dirents, NIDs, and compact inode modes without using DUT
helpers. Every derived image changes one real dirent byte and recomputes the
superblock CRC32C. `dump.erofs --ls --path=/` observes the mutated on-disk
`file_type`, while `dump.erofs --path=/NAME` reaches the same NID and reports
the inode kind from its mode. This gives 13 normal namespace resolutions from
real disk fields: eight known mismatches and five forward-compatibility cases.
All eight known mismatches are checksum-valid, fsck-clean images and yield
positive FreeBSD `EINTEGRITY` in the independent candidate oracle. Type zero,
nonzero dirent reserved bytes, and out-of-range type 8/255 remain accepted by
the DUT policy and candidate oracle. erofs-utils fsck separately rejects 8/255;
the evidence records that as `policy-reject` rather than falsely claiming
fsck-clean. P15-093 already freezes the FreeBSD behavior for these extension
values as `DT_UNKNOWN`, so fsck's stricter userspace policy does not override
the G11 compatibility boundary.
## Cache and Lock Boundary
`regular` and `regular-hard` are different namespace keys with the same real
NID. The first normal lookup can instantiate the vnode; the second name misses
that namecache key and reaches the existing `erofs_vget()` path, whose frozen
body checks `vfs_hash_get()` before inode decode. Mutating only the second
dirent to known directory type therefore provides the required cached-vnode
trigger without adding a readdir-time vget.
The generated candidate patch adds no lock, vget, hash, or recursive lookup
call. It reads immutable `vtype` after the existing child lookup, returns
positive `EINTEGRITY` for a known mismatch, drops the locked child with
`vput()`, and runs before `a_vpp` and namecache publication. Dotdot's existing
`vn_vget_ino()` path explicitly bypasses the validator because root `..` can
return the directory vnode itself; this preserves its parent-lock contract.
The parent/child lock order, readdir cookies, and VFS/VOP entrypoints are
unchanged. Readdir still contains no vnode lookup.
Linux supplies the format mapping: EROFS file type values match generic Linux
`FT_*`, and Linux readdir maps those values with `fs_ftype_to_dtype()`. The
candidate aligns that known-type mapping while retaining FreeBSD vnode types,
the FreeBSD 15 `__enum_uint8(vtype)` ABI type, positive errno, VFS locks, and
forward handling for unknown values.
## Replay Result
The authoritative command was:
```sh
timeout -k 10 240 tests/pre15/gates/P15-081.sh \
--base e2e3fb86b6fffcb01d6fd29c17dd95628ad070de \
--output OWNED_OUTPUT
```
It exits zero with `GO`: 14 generated images, eight known-match records
including the hardlink alias, eight known mismatches, five compatibility
cases, 13 normal entrypoint observations, one cached-vnode sequence, and 21
compiled prototype records. Two fresh output directories are byte-identical. The fixture-set
SHA256 is
`b562a7e42e139b16f3ce399d585aa7c373a66aa478a084bfed67b2b2aa9f2bd3`.
Key immutable evidence SHA256 values are:
- `result.json`: `4ec8e053c60ae75ae2fc4580a3d820a0452cf519f6b85376953135e18a7ec0ec`
- `oracle.json`: `932e558d3ead05572f38c89635bb10cae45393e42028031f81dfc7a56f2093f6`
- `lock-ledger.json`: `e8c2e6ba797fbcc83d832af6583d3f74f344750f6897111e7fe501130e577c13`
- `fixture-index.json`: `1a46b8ebfec16109c5d193001dc650a1b56000045e5d9e1e9b145fe7c5df9a16`
- `candidate.patch`: `427097da9304f5bedb770be8cab5f589706d316fb18a152ca16cb9d0663713fd`
- `prototype.c`: `1a295d18830c61a9708d465c3b1e415efb3ff8276100e4e32139bfa715195d8b`
- `normal-entry.tsv`: `b2841448068f70633ed0c02d901177a639cfed150f0e1c58977cc6c7b1e7313e`
- `SHA256SUMS`: `36b486617920b04ca87e88016e2fc0f519cda3a5f99dc675c9a89549615a02e3`
QEMU and the full feature suite were not run for this pre-source gate. The
gate uses host-created real EROFS images, independent binary parsing, normal
userspace namespace resolution, frozen DUT/FreeBSD control flow, and a
compiled prototype that is not linked into the DUT KLD.
+101
View File
@@ -0,0 +1,101 @@
# P15-083 Stage0 Decision
Status: `GO`. G04 authorizes B27 for all three non-LZ4 codecs. The source batch
must preserve the exact policies below and may not import the P15-062 LZ4 STOP
rule.
The gate is bound to DUT BASE
`68bbe94c44e35d53cec8ab55d007f40b01cf0502`, FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, erofs-utils 1.8.6, liblzma
5.8.1, zlib 1.3.1, and libzstd 1.5.7.
## Authoritative Run
The gate ran from `/work/erofs-freebsd-pre` as:
```sh
timeout -k 10 240 repo-pre-15/tests/pre15/gates/P15-083.sh \
--base 68bbe94c44e35d53cec8ab55d007f40b01cf0502 \
--output planning/pre15/evidence/20260815T142600Z-G04-P15-083/gate-output
```
It returned 0 and recorded `GO`. Evidence is rooted at
`planning/pre15/evidence/20260815T142600Z-G04-P15-083/`. The authoritative
script SHA-256 is
`6847541266b0668ad12442ffb54b67a772a29ed8ebe6fba0e1e25214db16b2c2`, the
input SHA-256 is
`b3c71b4ae03c6f9511246813952aab3178a1451433ece96f04562ec7cc6c1d1e`, and
the result SHA-256 is
`b9431b3819d64cdf940dff05ce7bc0e0bdf44c3ec647c90592052af5f7b3e544`.
## Decision Rule
P15-083 is GO only if LZMA, Deflate, and Zstd each have a reproducible real
EROFS fixture, an independent consumed-byte oracle, and a codec-specific policy
that distinguishes EROFS leading zero padding from unread nonzero bytes. Any
missing codec oracle, legal image rejected by the proposed full-stream policy,
or inability to distinguish trailing garbage makes the whole candidate STOP.
## Reproducible Corpus
erofs-utils 1.8.6 twice generates each fixed-time, fixed-UUID, root-owned,
xattr-free image from the same 151,552-byte source. Matching fsck extraction
reconstructs the complete source. The image hashes are:
| Codec | Image SHA-256 | Selected real extent | Leading zero padding | Stream |
|---|---|---:|---:|---:|
| LZMA | `c26cf15844fe45a21a746bacbad2ef551c683bb9b2538de7a7eced37d8eadff9` | 4,096 + 4,096 | 3,556 | 540 |
| Deflate | `39455150c3e999bb7ae6c36402c15a408a5679726b6d099e7d121e009ef43af6` | 28,672 + 4,096 | 1,479 | 2,617 |
| Zstd | `b905803f0e08500cc3c6cfe07a95fe027859165acae19ca8865856af256f32ca` | 4,096 + 4,096 | 1,092 | 3,004 |
The leading zero bytes are legal EROFS pcluster padding. They are removed by
the common dispatch before the codec callback, matching Linux
`z_erofs_fixup_insize()`. Every remaining legal stream reaches the format end,
consumes every byte, reproduces the selected logical extent, and leaves its
output guards intact.
## Per-Codec Policy
- Deflate: a full raw stream must reach `Z_STREAM_END` with no unread input.
The independent zlib decoder consumes 2,617 of 2,625 bytes after an 8-byte
nonzero tail and reproduces the output. fsck also accepts that mutation, as
does the Linux streaming loop when output is already full, but the bytes are
not EROFS leading padding and no mkfs fixture emits them. The audited FreeBSD
full policy rejects them with positive `EINTEGRITY` (97).
- LZMA: MicroLZMA stores no end marker and requires an exact compressed size.
The legal 540-byte stream consumes all bytes. Adding the same tail produces
liblzma status 9 after consuming 540 of 548 bytes; fsck rejects it and the
FreeBSD policy returns 97.
- Zstd: exactly one frame must finish with no unread input. The library finds
frame end after 3,004 of 3,012 bytes with the tail present; fsck rejects the
source-size mismatch and the FreeBSD policy returns 97. A concatenated or
skippable second frame is not EROFS pcluster padding.
This is deliberately codec-specific. It aligns Linux's non-LZ4 leading-padding
placement and stream completion semantics without copying the Linux wrappers'
implicit unread-byte acceptance into the FreeBSD provider path.
## Partial, Corruption, and Cleanup
The independent libraries decode real-stream prefixes that match the full
slice: Deflate produces 3,587 bytes after consuming 715, LZMA produces 4,096
after consuming 352, and Zstd produces 4,096 after consuming 1,457. Replacing
the final 64 stream bytes with zero begins strictly after each partial
consumption boundary. The same partial request still succeeds and matches;
full decode and fsck both fail for all three codecs, with the audited FreeBSD
policy mapping the full failure to 97. Removing the final compressed byte also
returns 97 for all three.
Every library path reports cleanup complete and unchanged guards. Frozen
`zdata.c` releases metadata or physical input after the callback, frees failed
decoded output, and publishes only successful output. The owned temporary tree
was removed. QEMU, protected PID 26318, port 9222, and the shared base image
were not touched. The full feature suite was not run.
## Consequence
B27 may now make these three existing policies explicit in the exact planned
write set and add TC176 host/QEMU coverage. It must preserve positive FreeBSD
errno, optional Zstd ABI, provider/GEOM ownership, input release, failed-output
free, and successful-output lifetime. The generated images, extracted files,
oracle source, and oracle binary remain temporary and are not committed.
+147
View File
@@ -0,0 +1,147 @@
# P15-086 Stage0 Decision
Status: `GO`. G04 and G05 authorize B28 partial subextent decoding for LZ4,
LZMA, and Deflate. Zstd is not authorized for partial decoding and must use the
exact full-decode fallback for ordinary strict subextent reads. Existing
partial-reference maps retain their bounded-prefix path. Shifted, interlaced,
unknown, and future backends also remain on full fallback unless a new gate
authorizes them.
The gate is bound to DUT BASE
`6bf5724619be70f805bbe7d1ba77dd70cdced69f`, FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, erofs-utils generations v1.4,
v1.7, and v1.8.6 for LZ4, erofs-utils 1.8.6 for the stream codecs, liblz4
1.10.0, liblzma 5.8.1, zlib 1.3.1, and libzstd 1.5.7.
## Authoritative Run
The gate ran once from `/work/erofs-freebsd-pre` as:
```sh
timeout -k 10 240 repo-pre-15/tests/pre15/gates/P15-086.sh \
--base 6bf5724619be70f805bbe7d1ba77dd70cdced69f \
--output planning/pre15/evidence/20260815T154915Z-G04-G05-P15-086/gate-output
```
It returned 0 and recorded G04=`GO`, G05=`GO`, and B28=`AUTHORIZED`.
Evidence is rooted at
`planning/pre15/evidence/20260815T154915Z-G04-G05-P15-086/`. The authoritative
script SHA-256 is
`591e407f20a6c8ab5d506329b3bd39ff7bd7f78b66ac86b9db2c45a498b31184`, the
input SHA-256 is
`62f7f505aef846fa85085039b2f47e252e11602e6ef1bf41f56841cdc9089a2a`, and
the result SHA-256 is
`33ef86b73447cd8c8099dc33901b9c4fff6c8ecedda3837d25cac9463a369d4f`.
## Decision Rule
Each codec is decided independently. A codec is partial-capable only when real,
twice-reproduced EROFS input decodes every requested range to the exact full
slice, reports a consumed-byte boundary before the end of the stream, leaves
guard pages unchanged, preserves positive FreeBSD errno, and reduces peak
temporary bytes by at least 20 percent without exceeding the fixed hard budget.
Failure of the benefit subgate selects exact full fallback for that codec; an
incorrect slice, unbounded memory, missing corruption boundary, or missing
oracle stops P15-086.
## Reproducible Corpus
All images are fixed-time, fixed-UUID, root-owned, and generated twice with
identical bytes. Matching fsck reconstructs every legal source.
| Codec | Generator | Image SHA-256 | Logical bytes | Physical bytes | Leading zero bytes | Stream bytes |
|---|---|---|---:|---:|---:|---:|
| LZ4 | v1.4 | `8f940673ed9f7e00efc5c0f06d41b2216748ea84256b740751aabff0d632de2b` | 1,038,906 | 4,096 | 0 | 4,096 |
| LZ4 | v1.7 | `93451e563f4eaf95b2690381074b5dfb3da0257691043c51619fd7d4c68dba7e` | 1,038,906 | 4,096 | 0 | 4,096 |
| LZ4 | v1.8.6 | `853649c78b159161421a6e833e0c516216382f3e8d135fc80aacd509ddb27b2c` | 1,038,906 | 4,096 | 0 | 4,096 |
| Deflate | 1.8.6 | `5f98ff39be30b1396be8164e54a8ae80b144d181d5295a640b2e7814406d7a2d` | 22,878 | 4,096 | 1 | 4,095 |
| LZMA | 1.8.6 | `887d4a9baf629533c8d512177a84ca256a20135157d29a201408534f63230889` | 151,552 | 4,096 | 3,556 | 540 |
| Zstd | 1.8.6 | `93f6beb46e77187ea8b0fc1ad3a5a1cb565d546d9ce40d51278f26d67187d5fa` | 151,552 | 4,096 | 1,092 | 3,004 |
The LZ4 corpus spans all three historical generators required by G04. Stream
codec consumption is reported by independent liblzma, zlib, and libzstd
decoders after the common EROFS leading-zero padding is removed. LZ4 is checked
by an independent raw parser and liblz4 partial/full output.
## Hard Budget and Benefit
The fixed input cap is 1 MiB, the decoded-output cap is 12 MiB, and the caller
cap is 1 MiB. Codec workspace caps are 0 for LZ4, 512 KiB for Deflate, 9 MiB
for LZMA, and 4 MiB for Zstd. These caps cover the frozen format maxima rather
than only the selected 4 KiB pclusters.
| Codec | Workspace full/partial | Hard cap | Baseline peak | Candidate peak | Reduction | CPU median partial/full | Decision |
|---|---:|---:|---:|---:|---:|---:|---|
| LZ4 v1.8.6 | 0 / 0 | 14,680,064 | 1,047,098 | 12,288 | 98.826% | 10,688 / 208,150 ns | `GO` |
| Deflate | 39,928 / 39,928 | 15,204,352 | 70,998 | 52,216 | 26.454% | 14,207 / 61,627 ns | `GO` |
| LZMA | 98,992 / 98,992 | 24,117,248 | 258,736 | 111,280 | 56.991% | 21,384 / 80,329 ns | `GO` |
| Zstd | 95,992 / 313,080 | 18,874,368 | 255,736 | 325,368 | -27.228% | 109,253 / 91,015 ns | `FULL_FALLBACK` |
All three LZ4 generations have the same 98.826 percent memory reduction; their
partial/full CPU ratios are 4.845, 4.632, and 5.135 percent. The gate collected
nine samples of 25 decodes per mode and stopped at that fixed sample count.
CPU timings are diagnostic because host scheduling produced visible outliers;
the authorization is based on deterministic peak temporary bytes. No noisy CPU
sample is used to rescue a codec that misses the 20 percent memory threshold.
## Ranges, Corruption, and Errno
Prefix, cross-page, middle, and tail requests all match the corresponding full
slice. The tail request reaches the extent end and therefore exercises full
fallback. Prefix consumption is 1,032 bytes for LZ4, 805 for Deflate, 352 for
LZMA, and 1,457 for Zstd. Corruption begins later at stream offsets 4,032,
4,031, 476, and 2,940 respectively. Every partial prefix remains byte-exact;
the same full read and every one-byte truncation return positive `EINTEGRITY`
(97). Real corrupted EROFS images fail matching fsck. All output guards and
codec cleanup checks pass.
Partial success is only evidence that the requested slice is correct. It is not
reported as verification of bytes after the consumed boundary or of the full
extent.
## State and Cleanup Model
B28 adds no persistent cache, owner, waiter, pool, or unmount-drain state. A
cache hit remains first. A gate-authorized strict subextent miss decodes into a
request-local bounded prefix and never publishes a partial cache entry. Full
requests, unsupported ordinary strict subextent codecs, shifted/interlaced
maps, and arithmetic fallback retain the existing exact full-decode/cache
policy. Existing partial-reference maps remain bounded-prefix and
cache-ineligible. Failure frees the local output after input release; success
publishes no shared partial state. Reclaim, eviction, unmount, and key reuse
therefore retain their existing ownership model.
The owned temporary tree was removed and the evidence SHA-256 manifest verifies
in full. No QEMU process was started. Protected PID 26318, port 9222, and the
shared base image were not touched. The full feature suite was not run.
## Consequence
B28 may implement bounded-prefix decode only for LZ4, LZMA, and Deflate in the
exact planned write set for ordinary strict subextent reads. Zstd and all
non-authorized backends must retain exact full fallback there without returning
an unsupported error; existing partial-reference behavior remains unchanged.
B28 acceptance owns D, the targeted TC176 host case, both KLD configurations,
and strict-timeout TC176 QEMU. It must preserve guards, consumed-byte semantics,
positive errno, cache eligibility, provider/GEOM ownership, and cleanup
ordering.
## B28 Execution Outcome
B28 source/test commit
`7a0d7e4a4047ac6d6130a3be5cb677cb94979fe7` has the exact 11-path actual
write set. LZ4, LZMA, and Deflate ordinary strict subextent reads decode a
bounded prefix; Zstd and non-authorized backends use exact full fallback.
Existing partial-reference maps remain bounded-prefix and cache-ineligible.
Final D, targeted host TC176 subset
`20260815T163642Z-host-B28-partial-1356298-0`, and targeted zstdio0/zstdio1 K2
`20260815T163515Z-host-B28-partial-1355895-0` pass. Earlier host and K2
`RUNNER_FAIL` records are retained with their corrected oracle/build findings.
The strict-timeout QEMU run
`20260815T163824Z-qemu-B28-partial-1356729-0` is `INFRA_BLOCKED` because guest
SSH did not become ready before the boot deadline; it did not reach the target
marker, so no guest TC176 runtime PASS is claimed. Owned cleanup passes,
protected PID 26318 and port 9222 were untouched, and the full feature suite was
not run. Complete evidence is under
`planning/pre15/evidence/20260815T161336Z-B28/`.
+95
View File
@@ -0,0 +1,95 @@
# P15-087 Stage0 Decision
Status: `STOP`. B12 is `STOP-NO-SOURCE`; no production source, B12 case, or
B12 fixture is authorized.
The final decision uses frozen DUT BASE
`c7d692acf9166f5c5e42335db2de64f0793ba8a2`, FreeBSD source HEAD
`106727738dcfb6c001b46f25363b91cece970085`, and
`mkfs.erofs (erofs-utils) 1.8.6`. The final gate script and input SHA256 values
are `349720b900ad2288153c9830f5f5ee84b8da707c06ffabcbd8e3e7a7e89f9e2a`
and `cb3aaa6c780c7b1efbd7c339f2b2da2ed8bf38d3ba32229dc17996807fd00787`.
## Workload and Independent Oracle
The gate deterministically creates 18,000 long-name regular files. Since
erofs-utils tailpacks the root directory even with `-E noinline_data`, the
generator moves the final 1,456-byte tail into one appended contiguous block,
changes only the root layout from flat-inline to flat-plain, increments the
superblock block count, and recomputes CRC32C. A second generation is
byte-identical and `fsck.erofs -d0` exits zero.
The independent parser does not call DUT code. It verifies a 3,880,368-byte
flat-plain directory at physical block 282, covering 948 contiguous 4 KiB
blocks and 18,002 entries. Its expected final cookie is 3,880,368 and its
record FNV64 is `427bb414efa99dc0`; the random offset is 1,937,408. The fixture
SHA256 is `3ecc5b706dd43b734c7e14a648a2bed2fb97a9f7963d4db703d15de81dc07a9d`.
The immutable `oracle.json` SHA256 is
`f247bbe5029301265342ff2098fcbd463a25e4752608826cbc1126d6620ef5d6`.
The generated prototype patch SHA256 is
`33b25131a611eec9c8731e88e4c258dfc92fe5897dd6781b6c95414a846dd2d1`.
Static extraction proves that it calls `breadn` only on the mapped backing
`devvp`, does not call `breadn` on the EROFS directory vnode, caps the window
at exactly 1 MiB, gates readahead on an offset-zero sequential readdir, and
adds no VM entrypoint, vnode lock, GEOM ownership operation, `cluster_read`,
or errno token. The `semantic-ledger.json` SHA256 is
`d26dbc37f4f34f65d07745a1b8069217d18545436d7f16f24e775e8c2db1a6b3`.
This closes the static design boundary but cannot replace runtime proof.
## Quantitative Decision
| Required G11 measurement | Required | Valid result | Decision |
|---|---:|---:|---|
| Cold sequential runs | 5 baseline + 5 prototype | 0 + 0 | FAIL: no median |
| Median latency improvement | at least 10% | not measurable | FAIL |
| Extra provider reads | at most 25% | not measurable | FAIL |
| Readahead window | at most 1 MiB | 1 MiB static cap | PASS |
| Random seek readahead | zero | 0 valid runtime samples | FAIL: not verified |
| Hash/cookie equality | exact independent oracle | 0 valid runtime samples | FAIL: not verified |
No run produced `runs.tsv`; therefore no latency, read-transfer, random-no-op,
or runtime hash/cookie value is claimed. It would be dishonest to infer GO
from the static prototype, from QEMU startup, or from build progress. Because
the workload could not be measured credibly and repeatably, G11's explicit
rule requires low-confidence STOP even though the 1 MiB static bound passes.
## Replay Ledger
The authoritative command shape was:
```sh
timeout -k 30 1200 tests/pre15/gates/P15-087.sh \
--base c7d692acf9166f5c5e42335db2de64f0793ba8a2 \
--output OWNED_OUTPUT
```
Existing evidence was retained long enough to hash and reconcile before
repository cleanup:
| Run ID | Result before any benchmark sample |
|---|---|
| `20260815T044720Z-G11-P15-087` | Superseded generator STOP: mkfs root remained flat-inline |
| `20260815T044924Z-G11-P15-087` | `INFRA_BLOCKED`: 12 boot-time providers exceeded SSH deadline |
| `20260815T045505Z-G11-P15-087` | `INFRA_BLOCKED`: FreeBSD did not enumerate PCI-hotplugged virtio-blk devices |
| `20260815T050003Z-G11-P15-087` | `INFRA_BLOCKED`: guest benchmark declaration error |
| `20260815T050520Z-G11-P15-087` | `INFRA_BLOCKED`: baseline KLD load did not reach workload |
| `20260815T051933Z-G11-P15-087` | `INFRA_BLOCKED`: phased baseline KLD load did not reach workload |
| `20260815T053543Z-G11-P15-087` | `INFRA_BLOCKED`: exact-basename baseline KLD load did not reach workload |
| `20260815T054540Z-G11-P15-087` | Aborted on instruction; owned process group terminated and audited |
Each completed QEMU ownership record reports that the owned port was free
after cleanup, protected PID 26318 retained the same identity, protected port
9222 was not used, and `/work/build/vm-freebsd-build.qcow2.bp` retained inode,
size, mtime, and ctime. The interrupted run used owned PID 1172175 and port
49845; both were absent after targeted process-group cleanup. Final process
audit found only protected QEMU PID 26318.
## Semantic and Batch Consequences
Runtime preservation of FreeBSD vnode, VM, locking, GEOM, errno, hash, and
cookie behavior was not established. Correctness may not depend on
readahead, so static plausibility is insufficient. P15-087 is STOP for Pre15,
B12 remains absent, and no host feature case, K0 build, TC183 QEMU acceptance,
or full feature suite is run. Rollback is the single gate decision commit;
there is no source commit to revert.
+142
View File
@@ -0,0 +1,142 @@
# P15-092 Stage0 Decision
Status: `STOP`. B15 is `STOP-NO-SOURCE`; terminal static review status is
`TERMINAL_REVIEWED_NO_SOURCE`. No production source, B15 case, or B15 fixture
was created. Historical acceptance remains `NOT_TESTED`.
P15-092 is the G11 zero-nlink candidate. The frozen BASE is
`b22dae8dc634c68db4ea89dccade91350614c139`, the FreeBSD source HEAD is
`106727738dcfb6c001b46f25363b91cece970085`, the erofs-utils source HEAD is
`7db78788b000999e2de88decd2ba90654f26171c`, and the generator is
`mkfs.erofs (erofs-utils) 1.8.6`. Linux EROFS identity is frozen by the three
source hashes in `P15-092-input.json` and the Pre15 semantic audit hash ledger.
The final gate script and input SHA256 values are
`5943b85864da9095174ef51d1eed032ec6f0a1766c6465cb9660ea20cbd46b1e`
and `0a6f6c76185644d3f5525a98429a310027556ef3cf7ebb43abb912f92e052bde`.
## Real Disk Oracle
The gate creates the same deterministic source tree twice in forced compact
form and twice in forced extended form. It uses fixed UUID, timestamp, worker
count, xattr policy, and inline-data policy. Both complete host output trees
are byte-identical.
An independent parser reads the real superblock, checksum span, metadata block,
root NID, directory entries, compact `i_nb.nlink`, extended `i_nlink`, and the
compact `EROFS_I_NLINK_1_BIT` rule. It then materializes seven images:
- compact and extended positive seeds;
- namespace-reachable compact, extended, and directory inodes with nlink zero;
- a root inode with nlink zero;
- an unreachable inode with nlink zero, whose former name is redirected to a
real hardlink NID and whose replacement link count is corrected to three.
Every derived image has a recomputed valid EROFS CRC32C. `dump.erofs --path`
resolves the actual directory edge and NID; no DUT helper or internal value
injection is used. All seven images, including all three namespace-reachable
zero-nlink images, pass `fsck.erofs -d0` with no error marker. The fixture-set
SHA256 is `99897d4fd81da015bdaa66a5dfdbf3f42e9e830882c0d8c979e691fa9209ee7a`.
Key deterministic host evidence SHA256 values are:
- `host-result.json`: `59a27e9389e2771c921d689f6b76caeb858f0fd28487a933529153727aac12e8`
- `disk-records.json`: `44bf0355dc5b34e93ec6868db9f9049de32500591b048a9016bf46aed9609485`
- `boundary.json`: `985df1236c6dd2016d6cb244b71578d36bd4cd10aa273b60a92d5bb6d24d5b60`
- `format-semantics.json`: `88efc1d7814103c044b36ff4dc3cac1dfa4da79f3d8955c682d1a60d9bfd9fcf`
- `fixture-index.json`: `4d2b69bcc31709ecb96cfafb41f5db8ffcc97215c3290cfa476d0b6c7fc27886`
- `source-anchors.json`: `ef9e6871d5fe1a3a6113f1172340269da4ea0c1692003aad0b0fee06ec4deb42`
## Linux and FreeBSD Semantics
Linux EROFS calls `set_nlink()` with the compact or extended disk value and has
no zero-nlink rejection in inode decode or super initialization. This is an
inode/link-count behavior, not a FreeBSD vnode publication rule. Linux and
FreeBSD both permit a live unlinked inode/vnode with link count zero; the
FreeBSD VM code explicitly treats `va_nlink == 0` as an unlinked mapping case.
The FreeBSD EROFS adapter has three distinct entry semantics:
1. `erofs_lookup()` owns a non-dot namespace edge and calls `erofs_vget()`.
2. `.vfs_vget = erofs_vget` is the raw NID entry used by VFS/root callers.
3. `erofs_fhtovp()` calls raw `VFS_VGET` and then maps `nlink == 0` to `ESTALE`.
Therefore Linux's direct `set_nlink()` behavior does not authorize either a
global FreeBSD rejection or a namespace-only rejection. The latter would be a
new FreeBSD validator policy, and the disk/fsck corpus supplies no format-level
rule requiring it.
## Publication Boundary
B09 moved the vnode constructor to `src/erofs_vnops.c`, but the frozen B15
write set is only `src/namei.c` and `src/inode.c`. `erofs_vget()` inserts the
constructing vnode in the hash, decodes the inode, marks it
`VSTATE_CONSTRUCTED`, and only then returns to `erofs_lookup()`.
A post-`erofs_vget()` check in `namei.c` is therefore too late to reject before
vnode publication. `vput()` alone does not perform the required `vgone`
cleanup. An unconditional `vgone()` cannot preserve an already cached vnode
obtained through raw `VFS_VGET`, because `erofs_vget()` does not return a
created-versus-hit indicator. Pre-reading through `erofs_read_inode()` would
duplicate complete inode decode and metadata I/O on every cold namespace
lookup, then decode the same inode again in `erofs_vget()`.
The four failed GO requirements are consequently:
- no format or cross-kernel rule requires reachable zero-nlink rejection;
- rejection before vnode publication is unavailable within the exact B15
write set;
- post-publication cleanup cannot preserve cached raw VGET semantics;
- pre-reading would duplicate full inode decode for a low-value validator.
Namespace and raw entrypoints are distinguishable, and the orphan needs no
mount-wide scan, but those two facts do not outweigh the failed safety,
compatibility, and cost requirements. P15-092 is therefore STOP rather than a
partial or expanded-write-set implementation.
## Replay and Infrastructure
The authoritative deterministic host command was run twice:
```sh
timeout -k 10 240 tests/pre15/gates/P15-092.sh \
--base b22dae8dc634c68db4ea89dccade91350614c139 \
--output OWNED_OUTPUT --host-only
```
Both runs exit `22` with `STOP` and have byte-identical host output trees.
One supplemental isolated runtime attempt used the standalone
`/work/debug-qemu/local/vm/freebsd-build-runtime.qcow2`, not the protected base
bp, with a fresh overlay, random port, exact baseline KLD, 180-second boot
deadline, 60-second guest command deadlines, 900-second inner timeout, and
1200-second outer timeout. Run
`20260815T083854Z-qemu-P15-092-gate-runtime-1219662-0` was
`INFRA_BLOCKED`: guest SSH did not become ready before the boot deadline, so no
runtime vnode result is claimed. The run used only PID `1219981` and port
`49795`; it did not own PID `26318`, port `9222`, or the protected base bp.
Cleanup is `PASS`: the owned QEMU PID stopped, port `49795` is free, the
overlay and case temp were removed, and the standalone base SHA256 remained
`ae09f47aef43cfd016049610e86bcf2073fdf70430961bcdb94662facac9f046`.
The runtime manifest and cleanup SHA256 values are
`2e9a4220dc86c5d8fa465e77e6d81e5e36c0f26338b70d71a84881af8fc02cb9`
and `0a499d8fa3397f73998c6c3c3ae9dfc9070f61267a86361ba96d1e8bc64cb392`.
Because the reproducible host gate already fails mandatory GO conditions, the
runtime infrastructure block does not defer or weaken the STOP decision. It is
reported separately and was not retried with another long-running VM. B15,
TC021, TC025, TC182, K0, and B15 acceptance QEMU are `NOT_RUN` by the mandatory
STOP-NO-SOURCE rule. The full feature suite was not run.
## Terminal Static Review
The 2026-08-17 review rechecked the current `repo-pre-15`, Linux EROFS
implementation, P15-092 plan, frozen write set, and post-STOP history. It
found no format or cross-kernel requirement for namespace zero-nlink
rejection, no new B15 consumer or test target, and no safe implementation
entry within the frozen write set. The detailed evidence is
`planning/pre15/evidence/20260817T-B15-terminal-review/VERDICT.md`.
The terminal review does not convert the historical STOP or acceptance state:
`STOP_NO_SOURCE` and `NOT_TESTED` remain the authoritative historical values.
+62
View File
@@ -0,0 +1,62 @@
# Pre15 Stage0 Execution Evidence
## Scope
B01 establishes the only Pre15 runner interface and the evidence contract used
by later gates, builds, directed tests, and smoke runs. It does not modify
`src/**`, run the full feature suite, or convert host parser results into KLD
runtime claims.
## Entrypoints
```sh
timeout -k 10 240 tests/pre15/run-host.sh CASE
timeout -k 30 1200 tests/pre15/run-build.sh zstdio0
timeout -k 30 1200 tests/pre15/run-build.sh zstdio1
timeout -k 30 1200 tests/pre15/run-qemu.sh CASE
timeout -k 30 1500 tests/pre15/run-smoke.sh final-four-codec
```
Cases are discovered as exact files under `tests/pre15/cases/`; no shared case
registry or historical result runner is consulted. `run-build.sh` reports a
Linux invocation as `INFRA_BLOCKED` because `build.sh` requires a native
FreeBSD host.
## Evidence
Set `PRE15_EVIDENCE_ROOT` to a new evidence parent. Every invocation creates a
unique run directory and never overwrites an earlier run. `manifest.json`
conforms to `tests/pre15/EVIDENCE-SCHEMA.json` and records exact argv, DUT and
source identities, worktree diff hash, fixture/module hashes, timestamps,
deadline, exit code, target marker, status, cleanup, and raw-output paths.
The only case statuses are `PASS`, `DUT_FAIL`, `RUNNER_FAIL`,
`INFRA_BLOCKED`, `STOP`, and `NOT_RUN`. A nonzero guest command without a target
marker is not a DUT failure. Cleanup failure always changes the run to
`RUNNER_FAIL`.
## Ownership
The runner records each owned process, path, forwarded port, SSH ControlMaster,
guest mount, md unit, loaded EROFS KLD, and base image before use. Cleanup runs
in reverse order and refuses to remove paths outside the current run directory.
QEMU always writes to a fresh overlay. The base image is read-only input for the
runner and its SHA256 must remain unchanged.
Mount, md, and KLD cleanup applies only to resources explicitly registered by
the current run. Kernel ZSTDIO is a kernel option, not an unloadable dependency.
## B01 Controls
- `B01-runner-selftest`: known-good, target-marked DUT mismatch, pre-target
command failure, SSH failure, QEMU early exit, timeout, owned PID cleanup, and
deliberate cleanup-boundary failure.
- `B01-g3-equivalence`: verifies the two archived script identities, compares
their source/artifact inventories with the stable helper, and repeats stable
generation.
- `TC162-xattr-legacy`: verifies checksum-valid legacy primary, explicit plain,
packed, and metabox prefix carriers plus exact single-field `EINTEGRITY`
negatives.
The authoritative B01 and initial gate verdicts are recorded under
`planning/pre15/evidence/` after execution from a committed B01 tree.
+101
View File
@@ -0,0 +1,101 @@
# Phase 0: B-ZSTD-001 Remediation
Status: `FIXED_STATICALLY`; runtime corruption-tail verification remains
`NOT_TESTED` in this remediation pass.
## Problem
Strict audit item B-ZSTD-001 / BUG-ZSTD-035 found that ordinary ZSTD extents
advertised subextent support. A prefix or middle read could therefore decode
only the requested output and accept `z_erofs_zstd_finish()` without checking
the remaining compressed stream. Corruption after the requested range could be
skipped.
P15-086 already authorizes partial ordinary reads only for LZ4, LZMA, and
Deflate. Its ZSTD result is `FULL_FALLBACK` because the partial candidate used
more temporary memory than full decode.
## Call Graph
For an ordinary mapped compressed read:
1. `z_erofs_do_read()` calls `z_erofs_decode_length()` with `mapoff` and
`want`.
2. `z_erofs_decode_length()` sets `partial=true` and `decoded_len=mapoff+want`
when the descriptor reports `supports_subextent` and the request ends before
the extent.
3. `z_erofs_decode_extent()` passes that length and mode to
`z_erofs_decompress()`.
4. `z_erofs_zstd_decompress()` fills the shortened output and calls
`z_erofs_zstd_finish()`.
5. `z_erofs_zstd_finish()` intentionally permits partial decoding without
requiring stream end. That behavior remains necessary for the separately
defined `EROFS_MAP_PARTIAL_REF` bounded-prefix path.
The smallest correct policy fix is to stop ordinary ZSTD reads at step 2:
ZSTD now advertises `.supports_subextent = 0`. `z_erofs_decode_length()` then
selects the full extent for ordinary prefix and middle reads. The explicit
`EROFS_MAP_PARTIAL_REF` branch still sets `partial=true` and remains unchanged.
## Changes
- Set the ZSTD descriptor capability to false in
`src/decompressor_zstd.c`; no finish validation was weakened or bypassed.
- Updated B27 assertions to preserve ZSTD partial-reference completion while
requiring ordinary ZSTD full fallback.
- Updated B28 source and extracted-policy assertions, host report, and QEMU
corruption decision so ZSTD is `FULL_FALLBACK`; LZ4/LZMA/Deflate policy is
unchanged.
## Verification
Commands and actual results:
- `git diff --check`: `PASS`.
- `sh -n repo-pre-15/tests/pre15/cases/B27-stream-tail.sh` and
`sh -n repo-pre-15/tests/pre15/cases/B28-partial.sh`: `PASS`.
- `PRE15_EVIDENCE_ROOT=/tmp/erofs-phase0-b28-20260818-r2 timeout -k 10 600
repo-pre-15/tests/pre15/run-host.sh B28-partial`: `PASS`; cleanup `PASS`,
target reached. Evidence:
`/tmp/erofs-phase0-b28-20260818-r2/20260818T070848Z-host-B28-partial-1780769-0/`.
The source audit reports `zstd=false`, ZSTD in `current_full_fallback`,
`current_partial=[deflate,lz4,lzma]`, and partial-reference preserved.
- `PRE15_EVIDENCE_ROOT=/tmp/erofs-phase0-b27-20260818 timeout -k 10 600
repo-pre-15/tests/pre15/run-host.sh B27-stream-tail`: `RUNNER_FAIL`, cleanup
`PASS`; the pre-existing B29 Deflate context-pool delta fails B27's frozen
exact-transform audit before the completion harness. This is not claimed as
ZSTD runtime evidence.
- P15-086 frozen gate result and policy were inspected, not rerun; its existing
recorded decision remains ZSTD `FULL_FALLBACK`.
The B28 host case generated and compared real EROFS fixtures and ran the
descriptor/decode-length source audit, but did not load a FreeBSD KLD or read
an image through the kernel. No full feature suite was run.
## Not Tested
`NOT_TESTED`: a real FreeBSD runtime read of an EROFS ZSTD image whose stream
is corrupted after an ordinary prefix or middle request. The required runtime
threshold is that the short ordinary read returns positive `EINTEGRITY` (97),
matching full fallback, while a valid ordinary prefix/middle/tail read matches
the source bytes.
`NOT_TESTED`: runtime confirmation that `EROFS_MAP_PARTIAL_REF` retains its
bounded-prefix semantics on a real image after this policy change.
## Risk
Ordinary ZSTD prefix and middle reads now decode the complete extent. This may
increase CPU or temporary output work relative to the previously incorrect
partial path, but it restores corruption visibility and matches P15-086 policy.
The changed tests do not alter test fixtures, image bytes, test environment, or
finish-error behavior.
## Runtime Gate
Before claiming Phase 0 runtime closure, run the existing focused B28 runtime
case with ZSTD enabled and verify valid prefix, middle, and tail reads, an
after-request-range corruption case returning `EINTEGRITY`, full corruption
returning `EINTEGRITY`, and the real partial-reference case. Record separate
`PASS`, `INFRA_BLOCKED`, or `NOT_TESTED` results; do not infer runtime behavior
from the extracted C harness.
+65
View File
@@ -0,0 +1,65 @@
# repo-pre-2 Maintenance Baseline
## Snapshot Identity
`repo-pre-2` was created from the tracked `repo-pre-1` tree without copying
working-tree-only files or build artifacts.
```text
planning baseline commit: ed47f1b5583a229e372f488b9492d1f6234aae98
snapshot creation parent: 109fe74d3fcef107db5869be1d030f4f12d1cfa6
snapshot creation commit: 73fa57924b549387400eeeb85354bbbbe770a962
repo-pre-1 source tree: 6a5a1a49d4a4e08f6ef1155de240500e63a15365
repo-pre-2 snapshot tree: 6a5a1a49d4a4e08f6ef1155de240500e63a15365
Linux comparison tree: b79b9a8b62f633e887a8b99075ff9412fabd7f83
```
The matching source and snapshot tree IDs establish byte-for-byte content,
file-mode, and path equivalence at snapshot creation. `repo-pre-1` and
`src-linux` are read-only references for pre2 work.
## Phase Scope
Pre2 phase 1 is L0 mechanical maintenance alignment. It permits only the
separately reviewed changes listed below:
1. Remove stale Linux-only declaration fragments from `src/xattr.h` while
preserving every active FreeBSD interface.
2. Remove six specified, unreferenced private constants from
`src/erofs_defs.h`.
3. Rename the private checksum helper in `src/super.c` without changing its
signature, body, call position, or behavior.
Each source change must remain an independent commit. This phase does not add
features, fix behavior, move responsibilities between files, reorder data
structures, alter ABI, or broadly reformat code.
## Required FreeBSD Differences
Linux and FreeBSD EROFS remain independent implementations. The following
platform boundaries are intentional and must not be replaced merely to make
the source look more similar:
- FreeBSD VFS, vnode, mount, namecache, pager, and NFS export interfaces.
- FreeBSD buffer/cache, GEOM provider, device I/O, locking, and allocation
APIs.
- FreeBSD xattr, ACL, errno, kernel-module, and `bsd.kmod.mk` conventions.
- FreeBSD build gates, including the current amd64 restriction and optional
`ZSTDIO` integration.
Linux naming and layout should be followed only when doing so preserves these
native contracts and makes cross-repository review easier.
## Excluded Work
This phase does not address vnode ownership after `insmntque()` failure,
large-run allocation and `uiomove()` limits, compact pblk validation, 48-bit
boundaries, decompressor descriptors, backend ABI, typed errno, xattr parsing,
ACL behavior, file splitting, build-system alignment, CI, or test tooling.
## Validation Status
Only static snapshot and Git allowlist checks were performed for the
initialization commits. No build, QEMU run, test script, smoke test, mount, or
runtime feature check was performed. All runtime capabilities therefore remain
`NOT RUN` for repo-pre-2 until a later, explicitly authorized validation phase.
+83
View File
@@ -0,0 +1,83 @@
# repo-pre-3 Maintenance Baseline
## Snapshot Identity
`repo-pre-3` was created exclusively from the Git-tracked `repo-pre-2` tree.
Untracked files, ignored files, build outputs, and working-tree-only content
were not copied.
```text
snapshot source commit: 034f409841d2edaf7f140fc7475a357c924a5930
snapshot creation commit: 865a702341a82e61ab5ca1d5708a28005277cc0a
repo-pre-2 source tree: c28e015c74f4d012dfd070f887fabf75d4d4cb16
repo-pre-3 snapshot tree: c28e015c74f4d012dfd070f887fabf75d4d4cb16
tracked files per tree: 281
```
The matching tree IDs establish path, file-mode, and blob equivalence at
snapshot creation. `repo-pre-2`, `repo-pre-1`, and `src-linux` are read-only
references for pre3 work.
## Phase Scope
Pre3 is an L1 mechanical naming-alignment stage. It contains four independent
source tasks:
1. Rename `erofs_init_xattr_prefixes` and
`erofs_cleanup_xattr_prefixes` to the Linux-like lifecycle names
`erofs_xattr_prefixes_init` and `erofs_xattr_prefixes_cleanup`.
2. Rename the private ACL helper `erofs_getacl` to `erofs_get_acl`.
3. Rename `erofs_close_device` to `erofs_release_device_info` while retaining
its FreeBSD implementation.
4. Rename the private mount-state destructor `erofs_free_mount` to
`erofs_sb_free` while retaining its complete FreeBSD cleanup body.
Only function names and their closed sets of declarations, definitions, and
call sites may change. Signatures, behavior, control flow, ordering, locking,
ownership, error handling, and file responsibilities must remain unchanged.
## Required FreeBSD Differences
- Xattr-prefix helpers continue to use `struct erofs_mount *`, not Linux
`struct super_block *`.
- ACL handling continues to use FreeBSD `struct vnode *`, `struct acl *`, and
integer errno conventions.
- Device teardown continues to release GEOM consumers, vnode references, and
cdev references in the existing order and under the existing locking rules.
It must not adopt the Linux callback signature or ownership model.
- Mount-state teardown continues to release all FreeBSD GEOM and private
filesystem state. A Linux-like name does not make the implementations or
lifecycle contracts interchangeable.
## Excluded Work
Pre3 does not include feature changes, correctness fixes, ABI changes, codec
work, xattr or ACL behavior changes, function reordering, structure layout
changes, file splitting, build-system changes, formatting cleanup, or repairs
to earlier planning and snapshot material.
## Git Workflow
The snapshot, this baseline document, and each of the four rename batches are
separate commits. Every commit must be pushed immediately to `xdm main` and
must not be squashed with another batch. Before each commit, its staged paths
must be checked against the task-specific allowlist. After each push, local
`HEAD`, `xdm/main`, and remote `main` must identify the same commit.
## Validation Status
Only static snapshot-equivalence, path-allowlist, Git-state, and documentation
checks were performed for pre3 initialization.
```text
build: NOT RUN
QEMU: NOT RUN
test scripts: NOT RUN
smoke tests: NOT RUN
mount/runtime: NOT RUN
feature validation: NOT RUN
```
No compile-time or runtime PASS is claimed by this document. Existing reports
copied from `repo-pre-2` are historical material and are not fresh validation
of `repo-pre-3`.
+84
View File
@@ -0,0 +1,84 @@
# Pre3 Smoke Test Report - 2026-08-12
## Result
The `repo-pre-3` plain/LZ4 differential smoke test **PASSed** within the scope documented below.
| Field | Value |
|---|---|
| Run ID | `20260812T110037.575683Z-693361-ac21acee` |
| Process exit code | `0` |
| Guest return code | `0` |
| Runner stages | `12/12 passed` |
| Elapsed time | `18m51s` |
| DUT outer commit | `1a2361bfae2f6b4e7fc4c89d97d05f44553a5ba7` (`1a2361b`) |
| DUT path tree | `902a32fbfc1274d4404174346953d19e41c61f1f` |
| DUT content SHA256 | `07aa856f0c0d9d0d084dbcb5d743cf4831a7f2ab884b51941db7d1840b248c60` |
| KLD SHA256 | `59650f03029afc31658dcf3386105d001122d7702b8144e7c5925d9d9ed4accb` |
| Base image SHA256 | `67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef` |
| Base image mode | `0444`, unchanged before and after |
Primary evidence:
- [result.json](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/result.json)
- [summary.txt](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/summary.txt)
- [guest evidence directory](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/guest-evidence)
- [guest-result.json](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/guest-evidence/guest-result.json)
## Invocation
```sh
./test.sh --dut /work/erofs-freebsd-pre/repo-pre-3 \
--base-image /work/tests-dev/lfs/freebsd-15-dev-src-20260811.bp \
--duration 10 \
--output demo-result/repo-pre-3-smoke-20260812
```
Evidence root:
```text
/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/
```
## PASS Scope
The successful run covered:
- FreeBSD guest boot and SSH readiness.
- DUT build with `WITH_ZSTDIO=0` and loading the exact generated `erofs.ko`.
- Creation and use of plain and LZ4 EROFS fixtures.
- Attach, mount, differential workload, unmount, and fixture cleanup.
- Four workers per fixture for five seconds each.
- Differential random, aligned, unaligned, full-file, and EOF reads.
- Directory enumeration, symbolic-link reads, and `fadvise` operations.
- Host and guest resource cleanup after the workload.
The guest-side stages and fixture identities are recorded in [stages.txt](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/guest-evidence/stages.txt) and [scenarios.txt](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/guest-evidence/scenarios.txt). Per-worker evidence is available in the same guest evidence directory.
## SKIPPED And Non-Claims
The following tests were **SKIPPED** and have no execution evidence in this run:
| Test | Area | Status |
|---|---|---|
| `TC079` | xattr prefix initialization, lookup, and cleanup | `SKIPPED` |
| `TC138` | ACL parsing and error paths | `SKIPPED` |
| `TC099` | multidevice mount and release paths | `SKIPPED` |
This report therefore does **not** claim that xattr prefix handling, ACL behavior, multidevice behavior, ZSTD, LZMA, DEFLATE, or the full feature/regression suite passed. The PASS result is limited to the plain/LZ4 differential smoke scope listed above.
## Integrity And Cleanup
- The DUT content hash, commit, and tree were identical before and after the run; the test did not modify `repo-pre-3`.
- The read-only base image remained mode `0444` with the same SHA256 before and after the run.
- New QEMU PID `693836` exited gracefully.
- Test port `10000` was released.
- The qcow2 overlay was deleted and the port lock became available.
- Guard PID `26318` and guard port `9222` retained the same process identity, start time, and command hash.
- The generated result directory under `tests-dev` is evidence only and was not committed.
## Honesty Review
The post-run honesty audit **PASSed**. It found no concealed source fix, result substitution, or exaggerated coverage claim. The guest log filename `repo22-build.txt` and related `repo22` stage labels are inherited labels in the test harness; they do not identify the DUT. The DUT is independently bound to `repo-pre-3` by the recorded commit, path tree, content hash, archived source, and generated KLD hash.
The complete machine-readable record remains authoritative for details not reproduced here: [result.json](/work/tests-dev/erofsstress/demo-result/repo-pre-3-smoke-20260812/20260812T110037.575683Z-693361-ac21acee/result.json).
+80
View File
@@ -0,0 +1,80 @@
# Pre5 Extraction Baseline
## Repository Baseline
- Outer starting commit: `6421919003ecfab5317dfa70959ab81cd9bbc990`
- Starting `repo-pre-5` tree: `f99462eee8898af209332ef202f8da07f2dd567e`
- Starting `repo-pre-5/src/super.c` blob: `c150d795df42ae4136fbd6e74c3b88903341b05a`
- Execution target: the existing `repo-pre-5` directory
- Source allowlist for the later extraction commits: `repo-pre-5/src/super.c`
The fixed tree and blob identifiers above were read from Git before this
document was created. Pre5 works directly on `repo-pre-5`; it does not create
another snapshot.
## Stage Scope
Pre5 is an L2-S private cleanup responsibility extraction stage. It has two
source tasks:
1. Extract the existing extra-device cleanup from `erofs_sb_free()` into
`static void erofs_free_dev_context(struct erofs_mount *em)`.
2. Extract the existing metabox and packed-inode cleanup from
`erofs_sb_free()` into
`static void erofs_drop_internal_inodes(struct erofs_mount *em)`.
Both tasks are statement movement only. They must preserve FreeBSD GEOM,
vnode, allocation, and ownership semantics. They must not change behavior,
ABI, features, error handling, locking, logging, conditions, release calls, or
object ownership.
## Protected Paths
The following paths must remain unchanged during Pre5 execution:
- `repo-pre-3/`
- `repo-pre-2/`
- `repo-pre-1/`
- `src-linux/`
- `planning/reject/`
- existing reports and other planning stages
Except for this baseline document, no path outside
`repo-pre-5/src/super.c` belongs in the Pre5 source commits.
## Cleanup-Order Invariant
The effective cleanup order must remain exactly:
```text
xattr prefixes
-> metabox
-> packed inode
-> extra devices in reverse order
-> primary device
-> mount state
```
The extraction must not add pointer clearing, conditions, assertions, logs,
locks, return values, error handling, or header declarations.
## Commit and Push Discipline
Each Pre5 commit must contain one planned logical task and must be pushed
immediately to `xdm main`. The commits must not be squashed. Execution must
stop if a push fails or if local, tracking, and remote commit identities
diverge.
## Validation Status at Baseline
| Validation item | Status |
|---|---|
| Static Git baseline capture | RECORDED |
| Source extraction tasks | NOT RUN |
| Build with `WITH_ZSTDIO=0` | NOT RUN |
| Build with `WITH_ZSTDIO=1` | NOT RUN |
| QEMU smoke testing | NOT RUN |
| Manual or automated tests | NOT RUN |
No build, QEMU run, feature validation, regression test, or runtime result is
claimed by this document.
+268
View File
@@ -0,0 +1,268 @@
# Pre5 Completion Report
Date: 2026-08-12
## Conclusion
| Area | Result | Notes |
|---|---|---|
| Planned source extraction | PASS | Both private cleanup helpers are present in `repo-pre-5/src/super.c`. |
| Static correctness review | PASS | No High or Medium findings; statement order and FreeBSD cleanup behavior are preserved. |
| Commit recoverability | PASS after correction | The initial split commits were non-destructively reverted and replaced by one independently revertible atomic source commit. |
| `WITH_ZSTDIO=0` build | PASS | Exit code 0, zero warnings, zero errors. |
| Module load | NOT RUN | The produced `erofs.ko` was not loaded. |
| QEMU functional testing | NOT RUN | No functional or regression test was run for Pre5. |
| Pre5 smoke test | DEFERRED | Deferred for a combined Pre5/Pre6 smoke run. |
Pre5 is complete for its planned source changes, static review, and required
build gate. This report does not claim runtime, feature, regression, or smoke
test coverage.
The final Pre5 source commit is
`4535cccaf1ca1837f0a2d1cca526e5f138a23c6d`. This report is created after that
commit and, when committed, its documentation commit will therefore follow the
final source commit.
## Scope
Pre5 directly modified `repo-pre-5`; no new repository snapshot was created.
The starting planning commit was:
```text
6421919003ecfab5317dfa70959ab81cd9bbc990
docs: plan repo-pre-5 cleanup extraction phase
```
The baseline documentation commit was:
```text
0666800fa529a8869757da85f2e69b8e1dd2c9f6
docs: record pre5 extraction baseline
```
The baseline recorded the following source identity:
```text
repo-pre-5/src/super.c blob:
c150d795df42ae4136fbd6e74c3b88903341b05a
```
The source scope was limited to extracting two existing cleanup regions from
`erofs_sb_free()` into private helpers. No feature, ABI, error handling,
locking, logging, ownership, or cleanup policy change was intended.
## Final Implementation
The final implementation is in [`src/super.c`](../src/super.c).
### Extra-device cleanup
`erofs_free_dev_context()` is a `static void` helper with exactly one
definition and one call. It contains only the existing extra-device cleanup:
```c
static void
erofs_free_dev_context(struct erofs_mount *em)
{
unsigned int i;
if (em->devs != NULL) {
for (i = em->extra_devices; i > 0; --i)
erofs_release_device_info(&em->devs[i - 1]);
free(em->devs, M_EROFS);
}
}
```
The reverse close order is unchanged. The primary device remains outside this
helper and is still released separately by `erofs_sb_free()`.
### Internal-inode cleanup
`erofs_drop_internal_inodes()` is a `static void` helper with exactly one
definition and one call. It contains only the existing metabox and packed
inode releases:
```c
static void
erofs_drop_internal_inodes(struct erofs_mount *em)
{
if (em->metabox_en != NULL)
free(em->metabox_en, M_EROFS);
if (em->packed_inode != NULL)
free(em->packed_inode, M_EROFS);
}
```
This remains a FreeBSD allocation cleanup path. It does not copy Linux
`iput()` or Linux inode-lifecycle semantics.
### Preserved cleanup order
The effective cleanup order remains:
```text
xattr prefixes
-> metabox
-> packed inode
-> extra devices in reverse order
-> primary device
-> mount state
```
The final caller is:
```c
static void
erofs_sb_free(struct erofs_mount *em)
{
if (em == NULL)
return;
erofs_xattr_prefixes_cleanup(em);
erofs_drop_internal_inodes(em);
erofs_free_dev_context(em);
erofs_release_device_info(&em->dif0);
free(em, M_EROFS);
}
```
Static expansion of both helpers produces the baseline statement sequence.
No condition, loop direction, release function, pointer clearing, lock, errno,
log message, return value, or ownership rule was added or changed.
## Commit Timeline
| Commit | Purpose | Result |
|---|---|---|
| `6421919` | Establish the Pre5 execution plan | Baseline planning point. |
| `0666800` | Record the source baseline and validation status | Documentation only. |
| `52dca2e` | Initially extract `erofs_free_dev_context()` | Static behavior correct. |
| `e9b9fb2` | Initially extract `erofs_drop_internal_inodes()` | Static behavior correct. |
| `c406813` | Revert `e9b9fb2` | Non-destructively removed the second helper first. |
| `20733bd` | Revert `52dca2e` | Restored the exact baseline `super.c` blob. |
| `4535ccc` | Atomically introduce both cleanup helpers | Final source implementation. |
The initial two source commits were behaviorally correct. The static audit
found a commit-organization defect: after `e9b9fb2`, the earlier `52dca2e`
could not be independently reverted from the final HEAD without conflict
because both commits edited the same tightly coupled `erofs_sb_free()` region.
No history rewrite or destructive reset was used. The correction was:
1. Revert `e9b9fb2` with `c406813`.
2. Revert `52dca2e` with `20733bd`.
3. Confirm that `super.c` returned to baseline blob
`c150d795df42ae4136fbd6e74c3b88903341b05a`.
4. Reintroduce both helpers in atomic commit `4535ccc`.
The final `super.c` blob is:
```text
03b92560bb5ef01f322b0d052f84bc3c577ef829
```
This is byte-for-byte identical to the correct source state at `e9b9fb2`.
The final commit `4535ccc` can be reverted without conflict and restores the
baseline blob.
The original plan preferred one source task per commit. That rule was adjusted
because the two extractions share one cleanup sequence and one caller region;
separate commits weakened independent rollback despite preserving behavior.
One atomic source commit provides a clearer and mechanically verifiable
rollback boundary without changing the planned source result.
## Static Review Evidence
The final independent static review result was PASS with no High or Medium
findings.
Verified properties:
- Only `repo-pre-5/src/super.c` differs from the source baseline.
- Both helpers are `static void` and each has one definition and one call.
- Extra devices are still released in reverse order before `em->devs` is
freed.
- Metabox cleanup still precedes packed-inode cleanup.
- The primary device remains a separate release after the extra-device
context.
- The mount state remains the final allocation released.
- Expanding both helpers recovers the original cleanup statement sequence.
- No header, exported interface, ABI, feature, condition, lock, errno, log, or
ownership change was introduced.
- No deferred correctness, codec, descriptor, formatting, or feature work was
mixed into Pre5.
- `repo-pre-1/`, `repo-pre-2/`, `repo-pre-3/`, `src-linux/`, planning files,
reject records, and earlier reports remained protected from source changes.
The source result therefore satisfies the intended L2-S responsibility
extraction while preserving the necessary FreeBSD cleanup implementation.
## Build Evidence
The required build gate was executed in the existing FreeBSD 15 VM with PID
`26318`. No new VM was started for this build.
Command:
```sh
FREEBSD_SRC=/root/pre5-build-gate-20260812T125645Z/freebsd-src \
WITH_ZSTDIO=0 ./build.sh
```
Result:
| Evidence | Value |
|---|---|
| Exit code | `0` |
| Compiler warnings | `0` |
| Compiler errors | `0` |
| Build log | `repo-pre-5/build/pre5-zstdio0-20260812T125645Z/build.log` |
| Module | `repo-pre-5/build/pre5-zstdio0-20260812T125645Z/erofs.ko` |
| Module size | `78,248` bytes |
| Module SHA256 | `0a2928a711715a22dfe243a536f514395acd52af3cec5515bd4bb003e42a14ac` |
The build artifacts are under the ignored `repo-pre-5/build/` directory and
must not be committed. The module was not loaded, and the build result alone
does not establish runtime behavior.
## Not Run And Deferred
| Item | Status | Meaning |
|---|---|---|
| Load produced `erofs.ko` | NOT RUN | Module load and unload behavior were not checked. |
| QEMU functional testing | NOT RUN | No functional test VM run was performed for Pre5. |
| Feature or regression suite | NOT RUN | No feature-completeness claim is made. |
| Pre5 standalone smoke test | DEFERRED | Deferred by user direction and risk assessment. |
| Combined Pre5/Pre6 smoke test | REQUIRED LATER | Must be performed after Pre6 before claiming runtime coverage. |
The Pre5 changes only extract existing private cleanup statements, and the
static review plus build gate found no source or compiler issue requiring an
immediate standalone smoke run. To avoid duplicating a relatively expensive VM
cycle, the smoke test is deferred and will be combined with Pre6.
The combined Pre5/Pre6 smoke run must cover at least:
1. Plain and LZ4 basic build, module load, mount, read, readdir, unmount, and
cleanup.
2. `TC099` multi-device success, missing-device failure, and device cleanup.
3. A packed-inode/metabox fixture mount and unmount path that exercises
internal-inode cleanup.
None of these deferred checks is claimed as passed by this report.
## Residual Risk
- The compiled module has not been loaded, so loader, symbol-resolution, and
unload behavior remain unverified for the final source commit.
- Failure and partial-initialization paths have only been reviewed statically.
- Multi-device cleanup has not been exercised at runtime after extracting
`erofs_free_dev_context()`.
- Packed-inode and metabox cleanup has not been exercised at runtime after
extracting `erofs_drop_internal_inodes()`.
- `WITH_ZSTDIO=1` was not built in this stage.
- No QEMU smoke, feature suite, stress test, or regression test has been run
specifically against the final Pre5 source state.
These risks are accepted for the current stage and are carried into the
combined Pre5/Pre6 smoke gate. Until that gate runs, Pre5 should be described
as static-review PASS and `WITH_ZSTDIO=0` build PASS, not runtime PASS.
+99
View File
@@ -0,0 +1,99 @@
# Pre6 Initialization Baseline
## Repository Baseline
- Outer starting commit: `bfed0431693e7798bdecacd78b7e6258c2d93c5a`
- Starting `repo-pre-6` tree: `b4a40164394b378adc947d6ad14dc7c845e79b2c`
- Starting `repo-pre-6/src/super.c` blob: `03b92560bb5ef01f322b0d052f84bc3c577ef829`
- Execution target: the existing `repo-pre-6` directory
- Source allowlist for later extraction commits: `repo-pre-6/src/super.c`
The tree and blob identities above were read from Git before this document was
created. Pre6 works directly on `repo-pre-6`; it does not create another source
snapshot.
## Stage Scope
Pre6 is an L2-I private mount-initialization responsibility extraction stage.
It contains three helper tasks:
1. Extract one decoded external-device initialization block into
`static int erofs_init_device(...)`.
2. Extract packed-carrier initialization into
`static int erofs_init_packed_inode(struct erofs_mount *em)`.
3. Extract metabox initialization into
`static int erofs_init_metabox_inode(struct erofs_mount *em)`.
These tasks move existing statements into private helpers. They must not change
features, behavior, public interfaces, error handling, allocation, cleanup,
logging, locking, GEOM operations, or object ownership.
## Source Commit Strategy
The source work is delivered in two commits:
1. One independent commit for `erofs_init_device()`.
2. One atomic commit for `erofs_init_packed_inode()` and
`erofs_init_metabox_inode()`.
Packed and metabox initialization remain one ownership unit because their
blocks are adjacent and a fragment-backed metabox can depend on the packed
carrier. No intermediate commit may contain only one internal-inode helper.
Each commit, including this baseline commit, must be pushed immediately to
`xdm main`. Execution must stop if local HEAD, `xdm/main`, and remote `main`
do not agree after a push.
## Protected Paths
The following paths must remain unchanged during Pre6 execution:
- `repo-pre-1/`
- `repo-pre-2/`
- `repo-pre-3/`
- `repo-pre-5/`
- `src-linux/`
- `planning/reject/`
- `1-code-similarity-review/`
- existing planning documents and reports
Except for Pre6 documents under `repo-pre-6/docs/`, no path outside
`repo-pre-6/src/super.c` belongs in the planned source commits.
## Behavior-Preservation Invariants
- Preserve the order of every moved statement and every helper call.
- Preserve all conditions, loop direction, values, types, casts, and return
values.
- Preserve every errno and log message without translation or normalization.
- Preserve allocations, frees, NULL assignments, and failure paths.
- Preserve the current owner and lifetime of every buffer, device, inode, and
mount resource.
- Keep `erofs_open_device()` unchanged as the FreeBSD GEOM open transaction.
- Keep external-device slot lookup and ascending iteration in
`erofs_scan_devices()`.
- Keep packed inode initialization before metabox inode initialization.
- Keep `erofs_mountfs()` as the common failure owner that calls
`erofs_sb_free()`.
- Add no rollback, validation, cleanup, logging, locking, or defensive changes.
- Keep all new helpers private and add no header declarations.
If an extraction requires a behavior, ownership, ordering, or errno change,
execution must stop instead of expanding Pre6 scope.
## Validation Status at Baseline
| Validation item | Status |
|---|---|
| Static Git baseline capture | RECORDED |
| Device initialization extraction | NOT RUN |
| Packed and metabox initialization extraction | NOT RUN |
| Static equivalence and independent revert gates | NOT RUN |
| Build with `WITH_ZSTDIO=0` | NOT RUN |
| Plain/LZ4 smoke test | NOT RUN |
| Complete `TC099` multi-device smoke test | NOT RUN |
| Positive `TC142` metabox smoke test | NOT RUN |
| Other build, QEMU, or runtime tests | NOT RUN |
No source extraction, build, QEMU run, feature validation, regression test, or
runtime result is claimed by this baseline document.
+55
View File
@@ -0,0 +1,55 @@
# Pre7 Helper-Alignment Baseline
## Repository Baseline
- Execution-start commit: `3f5f783b9f316af6d3887421251cc79909aed934`
- Starting `repo-pre-7` tree: `909ffaa0f586a51933f4220a8fe67d963d5b1dff`
- Starting `repo-pre-6` tree: `909ffaa0f586a51933f4220a8fe67d963d5b1dff`
- Starting `repo-pre-7/src/internal.h` blob: `253a30a595f9740ce97de865cc6f255f8eda2b74`
- Starting `repo-pre-7/src/inode.c` blob: `c753c14fdc48ae53e6dd2554cb9dc4a5b8a6c569`
- Starting `repo-pre-7/src/xattr.c` blob: `cef4db2a8d03be028c0344941cea60def9cd84ce`
- Execution target: the existing `repo-pre-7` directory
The tracked `repo-pre-7` baseline is an exact snapshot of `repo-pre-6` at the
execution-start commit. The identities above were read from Git objects at
that commit, not inferred from the live worktree.
Uncommitted changes were already present in `repo-pre-7/src/internal.h` and
`repo-pre-7/src/inode.c` when this document was written. This baseline does
not assess, validate, or claim completion of those changes.
## Exact Source Scope
Pre7 contains exactly two source tasks:
1. Rename the private inode-location helper from
`erofs_nid_to_offset()` to `erofs_iloc()` in
`repo-pre-7/src/internal.h` and `repo-pre-7/src/inode.c`. This is an
identifier-only alignment; the FreeBSD signature, implementation,
callers, metabox handling, overflow checks, and failure behavior remain
unchanged.
2. Extract the private `erofs_xattr_prefix()` mapping helper from
`erofs_xattr_namespace_prefix()` in `repo-pre-7/src/xattr.c`. The existing
wrapper retains FreeBSD namespace validation, namespace matching, errno
selection, and all existing get/list call sites.
No other source file, helper cleanup, behavior change, public interface
change, formatting pass, or deferred planning item belongs in Pre7.
## Validation Status
Per the current execution instruction, this baseline-recording step performs
no build or test activity. Historical Pre6 evidence is not a Pre7 result.
| Validation item | Status |
|---|---|
| Pre7 source implementation validation | NOT RUN |
| Static reference and behavior-matrix checks | NOT RUN |
| Independent source-commit revert checks | NOT RUN |
| `WITH_ZSTDIO=0` build | NOT RUN |
| QEMU smoke testing | NOT RUN |
| Manual or automated runtime tests | NOT RUN |
| CI testing | SKIPPED |
This is an implementation baseline only. It is not a completion report and
does not claim that either Pre7 source task is complete or correct.
+86
View File
@@ -0,0 +1,86 @@
# Pre8 Responsibility-Alignment Baseline
## Repository Identity
The following identities were read directly from Git objects at execution-start
commit `475b62437f89a8a98e0e3a1d1628481768525cb2`:
```text
execution-start commit: 475b62437f89a8a98e0e3a1d1628481768525cb2
execution-start tree: 3f0bc6be8324446440ce578f03d892c95f73591c
repo-pre-7 tree: b8f9af2cd55225c4348b79ff5910ae6fc83cd517
repo-pre-8 tree: b8f9af2cd55225c4348b79ff5910ae6fc83cd517
```
The equal `repo-pre-7` and `repo-pre-8` tree identities establish that
`repo-pre-8` is an exact tracked snapshot of `repo-pre-7` at the execution
start. This relationship is based on Git object identities, not a live
worktree comparison.
Relevant source identities at the same commit are:
```text
mode blob path
100644 e06822e363d9122a39256494bde7d12cfea1f7c9 repo-pre-8/src/decompressor.c
100644 ff595fe009679a4e950eb3743b2f152fac4aad31 repo-pre-8/src/decompressor_lzma.c
100644 f3e48cd2016608aaf84ed5e80151782a6c5513f7 repo-pre-8/src/decompressor_deflate.c
100644 23756af263ceb148ca50fbb8cd1ae80ba3fac4cd repo-pre-8/src/decompressor_zstd.c
100644 cac9ee15f81a1607a03fbab3466cf89c588872c8 repo-pre-8/src/internal.h
100644 2257a2299b3bcf3ab15978d723843f26d0b4b346 repo-pre-8/src/xattr.c
```
These values define the immutable comparison baseline for Pre8. Concurrent
or later worktree changes are not assessed by this document.
## Execution Scope
Pre8 is limited to the following private responsibility-alignment tasks:
1. Move `z_erofs_load_lzma_config()` from the core decompressor file to the
LZMA backend without changing its name, signature, validation order,
state updates, or error behavior.
2. Move `z_erofs_load_deflate_config()` to the DEFLATE backend with its
existing format checks, window-bit limits, state updates, and errors
preserved.
3. Move `z_erofs_load_zstd_config()` to the ZSTD backend while preserving the
availability check, configuration checks, window-log limit, diagnostics,
and `WITH_ZSTDIO` behavior.
4. Replace the duplicated inline and shared xattr traversal paths with shared
private iterator helpers while preserving lookup priority, list order,
namespace and errno timing, validation differences, output accounting, and
buffer ownership.
The source allowlist is restricted to the six files identified above. This
baseline file is the only documentation path used by the baseline step.
Pre8 does not include superblock extraction, a new `compress.h` contract,
descriptor tables, decode callback ABI changes, codec primitive renaming,
typed errno conversion, Linux page or folio abstractions, broad function
reordering, or any item already recorded under `planning/reject/`.
## Initial Validation Status
This document records the execution baseline only. No implementation,
correctness, build, or runtime conclusion is made here.
| Validation item | Initial status |
|---|---|
| Pre8 source implementation | NOT RUN |
| Source allowlist and protected-path audit | NOT RUN |
| `git diff --check` | NOT RUN |
| Loader definition, declaration, reference, and body-equivalence checks | NOT RUN |
| Xattr iterator invariant and behavior review | NOT RUN |
| Direct-parent restoration checks for source commits | NOT RUN |
| Final-HEAD inverse-patch compatibility checks | NOT RUN |
| `WITH_ZSTDIO=0` build | NOT RUN |
| `WITH_ZSTDIO=1` build | NOT RUN |
| Basic plain/LZ4 QEMU mount, read, readdir, and unmount smoke | NOT RUN |
| Pre7 deferred QEMU coverage | NOT RUN |
| Pre8 xattr QEMU coverage | NOT RUN |
| Pre8 codec QEMU coverage | NOT RUN |
| QEMU cleanup and zero-residual-state checks | NOT RUN |
| Completion and evidence-honesty review | NOT RUN |
Historical evidence from an earlier snapshot is not a Pre8 result. Every
status above remains `NOT RUN` until supported by fresh evidence from the
final Pre8 DUT. This file must not be interpreted as a completion report.
+314
View File
@@ -0,0 +1,314 @@
# Pre8 Completion and Validation
## Final Verdict
Pre8 overall status is **FAIL**.
The required implementation, static review, revert review, build matrix, basic
plain/LZ4 smoke, and selected test suite were all executed. The selected suite
did not pass its required gate. In particular, TC004 produced a definite DUT
failure while twelve other DUT verdicts were blocked by test-harness defects or
prior test residue. Under the status definition in `planning/pre8`, a required
action that ran and failed a gate is `FAIL`, not `PASS` or `PARTIAL`.
This overall verdict does not erase the narrower successful results:
| Validation layer | Verdict | Scope |
| --- | --- | --- |
| Source implementation | PASS | Planned loader placement and xattr iterator work is present in the final source tree. |
| Static source review | PASS | Allowlist, token-equivalence, references, xattr invariants, and protected paths passed review. |
| Revert and history review | PASS | Accepted replacement commits passed their defined direct-parent and final-HEAD checks; historical failures remain disclosed. |
| Four build configurations | PASS | `default`, `debug`, `zstdio0`, and `zstdio1` built and passed module load/unload smoke. |
| Basic plain/LZ4 smoke | PASS | Independent run `20260812T210345.913633Z-770842-a2868dd9`. |
| Selected 19-case suite | FAIL | Automation: 5 PASS / 9 FAIL / 4 ERROR / 1 NOT_RUN. Audited DUT: 6 PASS / 1 FAIL / 12 BLOCKED. |
| Pre8 overall | **FAIL** | A required selected-suite gate ran and failed. |
No claim is made that Pre8, repo22, the LZMA issue, or the test harness was
fixed by this validation work.
## Final DUT Identity
The final repository identity used by both selected-suite and basic-smoke
evidence is:
```text
parent repository commit: d1f5b686e8945d06b6e8c0a0188b2262de0b4ac2
parent repository tree: 19f450085cad49d06f6fe05d180313f13d4347f0
repo-pre-8 tree: 497736695fcc4285760b24c5954c34d32b8af49f
repo-pre-8/src tree: cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2
```
Selected-suite DUT archive:
```text
bbcfd8a6a28faba3f7c5c7e827bde5578774be6e08b83d05485d58ee16a7fb6f
```
Basic-smoke DUT archive:
```text
402459ba6a6f55696d06474f7494a69ef1a5fe49063d565cacadece37e8b9ce1
```
The different archive hashes belong to different runners and archive
procedures. Both reports bind their run to the same final repository commit
and `repo-pre-8/src` tree.
## Source Delivery
### Accepted implementation
The final accepted source changes are:
1. `3d28d96fc48753231b1899fce6189e586cf988c6`
(`erofs: unify inline and shared xattr iteration`)
2. `aac1412056f9ae7269f7c0f85c0233ade8cc662a`
(`erofs: move codec config loaders atomically`)
The xattr commit introduces common private inline/shared iteration while
preserving the reviewed lookup order, list order, namespace and errno timing,
inline/shared validation differences, buffer ownership, and output accounting.
The atomic loader replacement moves the LZMA, DEFLATE, and ZSTD configuration
loaders to their backend files. The loader bodies remain token-equivalent to
the baseline, dispatch calls remain in the core, decode functions are not
changed, and no Linux page/folio or descriptor ABI was introduced.
### Superseded split-loader history
The following pushed commits remain in history and must not be described as
accepted independent delivery units:
| Commit | Historical final-HEAD inverse result | Final disposition |
| --- | --- | --- |
| `dd7f2483d468ec4397a863c7a871391ea1bc2c4c` | FAIL: conflict in `decompressor.c`; `internal.h` auto-merged as staged content | Superseded by `aac1412`; failure permanently retained. |
| `0dba0ea223bee8626b8cd3371562f87ed975a99f` | FAIL: conflict in `decompressor.c`; `internal.h` auto-merged as staged content | Superseded by `aac1412`; failure permanently retained. |
| `7c47f6d` | PASS | Superseded with the split-loader group by `aac1412`. |
All three split commits passed direct-parent restoration at the point where
they were introduced. That fact does not overwrite the two final-HEAD inverse
failures. The split group was reverted by `96f041f`, then replayed as the one
accepted atomic commit `aac1412`. Commit `3d28d96` was not superseded.
### Static and revert verdict
Final static and revert status is **PASS**:
- the implementation remained within the planned source allowlist;
- protected trees and unrelated paths were unchanged;
- all three moved loader bodies matched their baseline implementations;
- declaration, definition, and dispatch-call closure was correct;
- the ZSTD availability and `windowlog > 10` rejection order was preserved;
- codec decode bodies were unchanged;
- the xattr iterator behavior and ownership matrix passed independent review;
- `aac1412` restored its direct parent exactly when reverted;
- `aac1412` independently reverted from final HEAD without changing xattr;
- `3d28d96` independently reverted from final HEAD without changing the
accepted loader layout;
- final `repo-pre-8/src` matched the reviewed functional source tree;
- `git diff --check` and worktree cleanliness checks passed at static handoff.
The detailed history record is
`repo-pre-8/docs/pre8-loader-history-correction.md`.
## Build Validation
The selected-suite runner built four module configurations. Every
configuration passed build completion, SHA256 recording, unresolved-symbol
checks, and an actual `kldload`/`kldunload` smoke before case execution.
| Configuration | Verdict | SHA256 | Size |
| --- | --- | --- | --- |
| `default` | PASS | `348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0` | 47,272 bytes |
| `debug` | PASS | `274189804511d249b34fbb3c223e9525b07c7c6266ec39d016ad2f33185001c7` | **UNKNOWN**; the harness did not persist size evidence before VM destruction |
| `zstdio0` | PASS | `348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0` | 47,272 bytes; byte-identical to `default` |
| `zstdio1` | PASS | `6cdb8e01fdac7805cb7ae12a9d1804b00d3e5cf37ef8b2f583137d36ee70426e` | 51,368 bytes |
The missing debug size is not reconstructed or inferred. No KLD or other
binary was committed.
## Basic Plain/LZ4 Smoke
Independent basic smoke run:
```text
run ID: 20260812T210345.913633Z-770842-a2868dd9
automation: PASS
runner exit: 0
DUT result: PASS for plain and LZ4 basic differential scope
guest: FreeBSD 15.0-RELEASE-p8 amd64
KLD SHA256: 527fe80ad7307d0200cfc4fec84c6fb86b914e89889e4a42ff0613fc24465015
KLD size: 47,272 bytes
```
All twelve runner stages passed. The run created a deterministic corpus with
256 regular files, 9 symlinks, and 43 directories. Both the plain and LZ4
images mounted read-only, passed a 307-entry differential preflight, completed
all four workers, and unmounted cleanly. The test QEMU exited gracefully, its
overlay and large temporary data were removed, and ports 10000 and 10001 were
released.
This result covers only plain/LZ4 basic mount, traversal, read, differential,
unmount, and cleanup behavior. It does not override the selected-suite
failures or blocked verdicts.
Evidence:
- `/work/tests-dev/erofsstress/demo-result/repo-pre-8-smoke-20260812/20260812T210345.913633Z-770842-a2868dd9/audit-report.md`
- `/work/tests-dev/erofsstress/demo-result/repo-pre-8-smoke-20260812/20260812T210345.913633Z-770842-a2868dd9/result.json`
- tests-dev commit `374b8e3ab4b0df24d6761802b70c694e2896d5fd`
## Selected Suite
Primary run:
```text
run ID: pre8-required-20260812T194817Z
cases: 19
automation: 5 PASS / 9 FAIL / 4 ERROR / 1 NOT_RUN
runner exit: 3
duration: 1892.44 seconds
audited DUT: 6 PASS / 1 FAIL / 12 BLOCKED
```
The automation status is the runner's result. The DUT status is a separate
evidence audit. An automation failure caused by a harness defect is not changed
to automation PASS. Likewise, a positive command observed before a missing
negative check does not make the full DUT case PASS.
### Per-case matrix
| Case | Automation | DUT | Audited observation |
| --- | --- | --- | --- |
| TC005 | FAIL | BLOCKED | Inline listing and both requested user xattr values succeeded. The missing-xattr `ENOATTR` assertion was never invoked because the truss output directory did not exist. |
| TC013 | PASS | PASS | Invalid NID returned `EINTEGRITY` (`ERR#97`) twice; mount, unmount, and zero-state checks passed. |
| TC079 | PASS | PASS | Packed user and trusted prefix values matched expected data and cleanup passed. |
| TC080 | FAIL | BLOCKED | Four packed-prefix values were correct. The missing-suffix `ENOATTR` assertion was not invoked because the truss output directory was absent. |
| TC082 | FAIL | BLOCKED | ACL bytes and ordering were observed, but automation compared spaced hex against an unspaced prefix; the user-namespace negative assertion also was not invoked. |
| TC067 | FAIL | PASS | Both shared files, the local xattr, and listing matched the fixture. Automation incorrectly expected one additional trailing `!`. |
| TC068 | FAIL | BLOCKED | Shared listing succeeded. The nonexistent lookup was not invoked because truss could not create its output file. |
| TC069 | PASS | PASS | Three shared xattrs were listed and all exact values were read; cleanup passed. |
| TC081 | PASS | PASS | Metabox shared, shared-prefix, and packed-prefix xattrs were listed and read from both files; cleanup passed. |
| TC117 | FAIL | BLOCKED | Both corrupt fixtures mounted and valid local shared data remained readable. Corrupt-entry errno assertions were not invoked because truss failed first. |
| TC135 | PASS | PASS | Metabox, packed, and primary-prefix fallback values matched expected data. |
| TC138 | FAIL | BLOCKED | Valid unordered and empty-header ACLs were read. Three malformed ACL assertions were not invoked because the scratch directory was absent. |
| TC140 | FAIL | BLOCKED | Positive compressed and fragment metabox carrier hashes and xattrs passed. Four negative mount commands were not invoked because truss failed opening its trace. |
| TC142 | FAIL | BLOCKED | Positive fragment-backed compressed metabox hash and xattrs passed. Four negative mount commands were not invoked for the same reason. |
| TC004 | ERROR | **FAIL** | LZMA image mounted, but SHA256 of the mounted 8 MiB `level.dat` timed out after 30 seconds. The process remained running and left the mount and `md0` busy. |
| TC084 | ERROR; follow-up FAIL | BLOCKED | Primary run was contaminated by TC004 residue. Fresh follow-up mounted LZ4 and found the target, but guest `dump.erofs` was absent before content verification. |
| TC102 | ERROR; follow-up FAIL | BLOCKED | Primary run was contaminated by TC004 residue. Fresh follow-up mounted DEFLATE, but missing guest `dump.erofs` prevented algorithm and content verification. |
| TC105 | ERROR; follow-up FAIL | BLOCKED | Primary run was contaminated by TC004 residue. Fresh follow-up mounted ZSTD with `zstdio1`, but missing guest `dump.erofs` prevented verification. |
| TC145 | NOT_RUN; follow-up ERROR | BLOCKED | Both zstdio modules built and initial load smoke passed. The follow-up queried and unloaded module name `erofs` instead of the loaded artifact name, so functional mount/read gates were not reached. |
### Definite DUT failure
TC004 is a definite observed DUT failure for this test run:
1. `lzma-level6.erofs` attached as `md0`.
2. The read-only EROFS mount succeeded.
3. The mounted `level.dat` existed.
4. SHA256 of the source file completed and returned
`ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`.
5. SHA256 of the mounted 8 MiB file did not finish within 30 seconds.
6. The timed-out `sha256` process remained alive.
7. Unmount and `mdconfig -d` returned `Device busy`.
This evidence does **not** prove that Pre8 introduced the problem. No matching
Pre7 or pre-change comparison run with the same image, guest, module build,
and command is available. The issue is recorded separately in
`issues/pre8-lzma-read-timeout.md` without an attribution claim.
### Codec follow-up
Fresh follow-up run:
```text
run ID: pre8-codec-followup-20260812T202059Z
cases: TC084, TC102, TC105, TC145
automation: 3 FAIL / 1 ERROR
runner exit: 1
duration: 1136.92 seconds
audited DUT: 4 BLOCKED
```
The follow-up isolated these cases from TC004 residue, but it did not produce
codec functional PASS verdicts. TC084, TC102, and TC105 were blocked by absent
guest `dump.erofs`. TC145 was blocked by incorrect module-name lookup/unload
and cleanup behavior in the harness. These results remain FAIL/ERROR in
automation and BLOCKED for the DUT.
The harness findings are recorded in
`issues/pre8-test-harness-blockers.md`. No harness modification is part of
Pre8 source delivery.
Selected-suite evidence:
- `/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/summary.md`
- `/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/summary.json`
- `/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/pre8-required-20260812T194817Z/summary.json`
- `/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/pre8-required-20260812T194817Z/evidence/`
- `/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/pre8-codec-followup-20260812T202059Z/summary.json`
- `/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/pre8-codec-followup-20260812T202059Z/evidence/`
- tests-dev commit `46e6840f1b8918414af76c9a5731322c697eee8b`
## Fixtures and Immutable Inputs
The common base image was not modified:
```text
path: /work/tests-dev/lfs/freebsd-15-dev-src-20260811.bp
format: qcow2
mode: 0444
size: 16,515,530,752 bytes
SHA256: 67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef
```
Selected primary fixture deployment identities:
| Group | Manifest SHA256 | Checksum-list SHA256 |
| --- | --- | --- |
| G1 | `9ffe9e695a6544cb010a5d4ba1e0c2c09d7455e6d7e28cc81da54d2dd954f2f2` | `db416004ff6b00ad286e50da506168b35a544a7da5619042225198843e4c741c` |
| G4 | `6200842757dae30f3a5b0683d726d339a24e55dff98cd084597462289a9308aa` | `229c84c5436cf9b5b659ded375097fba13621c20a2cf2d845f146df737836622` |
| G5 | `c9540baafb53b3783233c3950f4d8548c28585cd32335dadc14db613692a8978` | `d08138b1bd51579cbf5461a73abce9d7fddc9cdb8ace1329c9f1581d41e1095f` |
Follow-up G5 deployment identities:
```text
manifest: 9be85e48115f2dd2cae058f2a4ea7438512f28e647c30476d049d6b8025b02fd
checksum list: 7513425c98400f641523083bf0d06b2d548aa3163dfcdfe980dca2b0e1900acd
```
Deployment hashes can include run-specific metadata. Image, source, generator,
and checksum evidence remains beneath each run's `fixture-evidence/` and
per-case evidence directories.
## Cleanup and Guard Integrity
- The selected primary and follow-up QEMUs were force-destroyed by the runner
after their final errors.
- Their run-owned overlays were deleted.
- Ports 10000 and 10001 were released and closed.
- The independent basic-smoke QEMU shut down gracefully and its overlay was
removed.
- Guard PID 26318 retained its identity and TCP port 9222 remained active.
- Base-image SHA256, mode, and size were unchanged before and after the runs.
- The DUT commit and source tree remained unchanged and clean.
- No DUT source, repo22 source, base image, test source, fixture generator, or
`oldtests` content was modified during validation.
Cleanup of the disposable VM does not change TC004's case-level cleanup result:
the case itself failed to terminate its read process and could not unmount or
detach `md0`. VM destruction only removed the run environment afterward.
## Final Handoff
Pre8 delivers the planned source-responsibility changes and passes static,
revert, build, and basic plain/LZ4 smoke validation. It does not satisfy the
full required validation gate because the selected suite contains one definite
DUT failure and twelve blocked DUT cases. The honest final status is therefore
**FAIL**.
Open follow-up records:
- `issues/pre8-lzma-read-timeout.md`
- `issues/pre8-test-harness-blockers.md`
+74
View File
@@ -0,0 +1,74 @@
# Pre8 Loader History Correction
## Scope
This record corrects the commit organization of the Pre8 codec configuration
loader moves. It does not report a source behavior defect and does not rewrite,
amend, squash, or hide any pushed commit.
The affected historical commits are:
```text
dd7f2483d468ec4397a863c7a871391ea1bc2c4c LZMA loader move
0dba0ea223bee8626b8cd3371562f87ed975a99f DEFLATE loader move
7c47f6d ZSTD loader move
3d28d96fc48753231b1899fce6189e586cf988c6 xattr iterator change
```
## Recorded Results
Each of the three loader commits restores its direct parent's complete tree
when reverted at the commit where it was introduced. Those direct-parent
revert checks are `PASS`.
When each commit is reverted independently from final functional commit
`3d28d96`, the results are:
| Commit | Final-HEAD independent revert | Result |
|---|---|---|
| `dd7f248` | Unmerged conflict in `decompressor.c`; `internal.h` auto-merged as a staged modification | FAIL |
| `0dba0ea` | Unmerged conflict in `decompressor.c`; `internal.h` auto-merged as a staged modification | FAIL |
| `7c47f6d` | Applies without conflict | PASS |
| `3d28d96` | Applies without conflict | PASS |
The two failures are caused by overlapping patch context in `decompressor.c`.
In both cases, `internal.h` is automatically merged as a staged modification,
not left as an unmerged conflict. This is a commit organization problem, not
evidence of a codec implementation or xattr behavior problem.
The `dd7f248` and `0dba0ea` final-HEAD results remain permanently recorded as
`FAIL`. The `7c47f6d` result remains `PASS`, but that commit is superseded with
the other two loader commits so the loader responsibility change has one
atomic acceptance unit.
## Correction Strategy
The three split loader commits will be reverted in reverse order without
rewriting history. Their exact combined five-path change will then be replayed
as one atomic replacement commit. The replacement is limited to:
```text
repo-pre-8/src/decompressor.c
repo-pre-8/src/decompressor_lzma.c
repo-pre-8/src/decompressor_deflate.c
repo-pre-8/src/decompressor_zstd.c
repo-pre-8/src/internal.h
```
Commit `3d28d96` remains accepted and is not superseded. Its `xattr.c` content
must remain unchanged through the correction.
The new acceptance objects are:
1. the atomic replacement loader commit, which must restore its direct parent
exactly when reverted and must independently revert from final `HEAD`
without affecting xattr content; and
2. existing xattr commit `3d28d96`, which must independently revert from final
`HEAD` without affecting the final loader layout.
## Validation Status
This document records static Git evidence only. No build or QEMU test has been
run for this history correction. Build and runtime validation remain
`NOT RUN` until later Pre8 validation produces fresh evidence from the final
DUT.
+191
View File
@@ -0,0 +1,191 @@
# Pre9 LZMA Diagnostic Implementation Baseline
## Baseline Meaning
The Pre9 planning phase ended when the planning documents were committed at
`d1ee2e05b82b5e8a8927861f1e981710532f2f23`. This file starts the
implementation phase and records identities only. It does not claim that the
TC004 root cause is known, that Pre8 introduced the observed timeout, or that
any source or test-harness fix has been selected or implemented.
All values below were read from Git objects or the named evidence files on
2026-08-13 UTC. Historical Pre8 evidence is context for controlled Pre9
diagnosis, not a Pre9 test result.
## Main Repository Identity
Repository: `/work/erofs-freebsd-pre`
```text
branch: main
execution-start commit: d1ee2e05b82b5e8a8927861f1e981710532f2f23
execution-start tree: 95a55ff912d007ca40bd578057e8a4cc6a21bbd9
xdm/main: d1ee2e05b82b5e8a8927861f1e981710532f2f23
remote xdm main: d1ee2e05b82b5e8a8927861f1e981710532f2f23
initial dirty entries: 0
```
Snapshot identities at the execution-start commit:
```text
snapshot subtree src tree
repo-pre-7 b8f9af2cd55225c4348b79ff5910ae6fc83cd517 dc203534d7b5721905f8538026e4c59346106020
repo-pre-8 1e24c992a5b071e6fdabfdc42d342b6ed5a91cf5 cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2
repo-pre-9 1e24c992a5b071e6fdabfdc42d342b6ed5a91cf5 cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2
```
The equal Pre8 and Pre9 object identities establish that Pre9 starts as an
exact tracked snapshot of Pre8. Pre7 is intentionally different and remains
the earlier controlled comparison point.
## Conditional Source Allowlist Identity
The primary conditional allowlist is `zdata.c`, `decompressor_lzma.c`, and
`internal.h`. The remaining three files require the additional decision gates
defined by the plan. Their baseline modes and blobs are:
```text
gate mode blob path
primary 100644 54c1c88e5c368e7b5f6a16188c48406767850821 repo-pre-9/src/zdata.c
primary 100644 688acd704e28daa46369fa2e63e895bce5262774 repo-pre-9/src/decompressor_lzma.c
primary 100644 9821fe3a8b5fcaaeb3939d191f15e9ce637cc8d1 repo-pre-9/src/internal.h
extra 100644 ca5d33cd42145159d71e153f115128ac75258708 repo-pre-9/src/inode.c
extra 100644 b233f138d036a2b37394946b382034955dc2a1df repo-pre-9/src/erofs_vnops.c
extra 100644 51170741a0fb5eced814db898c1a233e8a23088a repo-pre-9/src/zmap.c
```
This identity table authorizes no source change by itself. Source edits remain
conditional on controlled evidence and the applicable decision gate.
## tests-dev and Base Image Identity
The external test repository was inspected without modification:
```text
repository: /work/tests-dev
branch: main
current commit: 374b8e3ab4b0df24d6761802b70c694e2896d5fd
current tree: c438e72ebe94a6f10801999bfbdd36b43134d471
xdm/main: 374b8e3ab4b0df24d6761802b70c694e2896d5fd
initial dirty entries: 27 untracked paths
dirty inventory SHA256: bca9ca20e85698d04dad820a11bd0bbb117492c967d6fd338e7acf61bb8fa957
```
The pre-existing tests-dev dirty inventory was:
```text
demo-result/FINAL-REPORT.md
demo-result/audit-checklist.md
demo-result/audit-findings.md
demo-result/current-status.md
demo-result/fix-plan.md
demo-result/fix-verification-report.json
demo-result/progress-report.md
demo-result/repo-pre-6-smoke-20260812/
demo-result/repo22-full-20260811T065312Z/
demo-result/repo22-full-20260811T071634Z/
demo-result/repo22-full-20260811T073716Z/
demo-result/repo22-full-20260811T082907Z/
demo-result/repo22-full-20260811T095621Z/
demo-result/test-execution-summary.md
erofsstress/demo-result/repo-pre-3-smoke-20260812/
erofsstress/demo-result/repo-pre-6-smoke-20260812/
guest/setup-build-env.sh
lfs/FreeBSD-15.0-RELEASE-src.txz
scripts/check-kernel-src.sh
scripts/complete-setup-and-test.py
scripts/install-kernel-sources.py
scripts/manual-fix.sh
scripts/quick-install-src.py
scripts/run-final-test.sh
scripts/setup-build-env-simple.py
scripts/setup-build-env.py
scripts/simple-install-kernel-src.sh
```
These paths are not owned by this baseline step and must not be staged,
modified, or removed as part of it.
Immutable base image identity:
```text
path: /work/tests-dev/lfs/freebsd-15-dev-src-20260811.bp
format: QEMU QCOW2 Image (v3)
mode: 0444
size: 16,515,530,752 bytes
SHA256: 67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef
```
The base image is an input only. It must not be modified during Pre9 testing.
## Historical TC004 Evidence Identity
The prior observation is preserved by tests-dev evidence commit
`46e6840f1b8918414af76c9a5731322c697eee8b`, tree
`7178de49f3f429796d6de71a910d71bfccd5745a`, with subject
`evidence: record repo-pre-8 required smoke`.
```text
run ID: pre8-required-20260812T194817Z
case: TC004 - LZMA Compressed File Read
historical DUT commit: d1f5b686e8945d06b6e8c0a0188b2262de0b4ac2
historical DUT src tree: cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2
DUT archive SHA256: bbcfd8a6a28faba3f7c5c7e827bde5578774be6e08b83d05485d58ee16a7fb6f
module SHA256: 348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0
fixture image: G5/images/lzma-level6.erofs
fixture image SHA256: 32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9
source file: G5/sources/levels/level.dat
source file SHA256: ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461
deployment manifest: c9540baafb53b3783233c3950f4d8548c28585cd32335dadc14db613692a8978
fixture checksum hash: d08138b1bd51579cbf5461a73abce9d7fddc9cdb8ace1329c9f1581d41e1095f
TC004 evidence SHA256: de6982a11fa368fbaad7d837586daa899789cd4cd8b9f698974a3ef4e251811f
```
Evidence paths:
```text
/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/pre8-required-20260812T194817Z/summary.json
/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/pre8-required-20260812T194817Z/evidence/TC004/TC004_evidence.json
/work/tests-dev/demo-result/repo-pre-8-smoke-20260812/summary.md
/work/erofs-freebsd-pre/issues/pre8-lzma-read-timeout.md
```
The historical automation verdict was `ERROR` and the audited DUT verdict was
`FAIL`: mount and lookup succeeded, but hashing the mounted 8 MiB `level.dat`
did not finish within 30 seconds. PID 95693 remained active, and the mount and
`md0` stayed busy until the disposable VM was destroyed. Attribution remains
`UNKNOWN`. These facts neither prove a Pre8 regression nor select a Pre9 fix.
## Initial Implementation Status
| Item | Status |
|---|---|
| Pre9 planning | COMPLETE |
| Pre9 implementation baseline | RECORDED |
| tests-dev TC004 prerequisite corrections | NOT RUN |
| tests-dev remaining full-suite prerequisites | NOT RUN |
| Controlled Pre7 reproduction | NOT RUN |
| Controlled Pre8 reproduction | NOT RUN |
| Controlled unchanged Pre9 reproduction | NOT RUN |
| Manual SSH diagnosis | NOT RUN |
| Hypothesis decision gates | NOT RUN |
| Source decision | NOT RUN |
| Optional diagnostic instrumentation | NOT RUN |
| Pre9 source implementation | NOT RUN |
| Source static review | NOT RUN |
| Source direct-parent revert gate | NOT RUN |
| Source final-HEAD inverse gate | NOT RUN |
| `WITH_ZSTDIO=0` build | NOT RUN |
| `WITH_ZSTDIO=1` build | NOT RUN |
| TC004 | NOT RUN |
| Extended LZMA matrix | NOT RUN |
| Plain/LZ4 smoke | NOT RUN |
| Full regression | NOT RUN |
| QEMU cleanup and zero-residual-state checks | NOT RUN |
| Pre9 completion and evidence-honesty review | NOT RUN |
| Pre9 overall | NOT RUN |
No diagnostic command, tests-dev prerequisite, controlled comparison, source
change, build, QEMU run, or manual SSH investigation has been performed for
Pre9 at this baseline. Fresh evidence is required before any status above can
advance or any root-cause or repair claim can be made.
+132
View File
@@ -0,0 +1,132 @@
# Pre9 decoded extent cache final manual validation
Date: 2026-08-13
## Scope
This run validated the mount-scoped decoded LZMA extent cache at enclosing
repository commit `cdcf276d12169a672d2de42996532a470ac061d9`.
Controlled identities:
- `repo-pre-9` tree: `9d7eaf63a1c5d07320af3ef39930fc0a3530dd11`
- `repo-pre-9/src` tree: `e657e8a63b097b061670f75ca01947a568ef33d5`
- guest-built KLD SHA-256:
`473a6205f43905972f2417609d43f67ab2cb51ea79bc1716fffa5c1914ff85af`
- LZMA image SHA-256:
`32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9`
- LZMA source SHA-256:
`ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`
The existing fresh QEMU process on host port 10030 was reused. No second VM
was created. The guest used FreeBSD 15.0-RELEASE-p8 under QEMU TCG with 6 GiB
RAM and four virtual CPUs. The KLD was built in the guest with
`make WITH_ZSTDIO=0`.
The VM used the existing base image
`/work/tests-dev/lfs/freebsd-15-dev-src-20260811.bp`. Its previously audited
SHA-256 is
`67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef`.
This run relied on that established integrity record and did not repeat the
approximately 16 GiB image hash, so base-image hashing did not block the final
validation.
## Verdict
| Area | Verdict | Result |
| --- | --- | --- |
| Guest build, KLD load and fixture mounts | PASS | Build returned zero, `kldload` returned zero and both LZMA and LZ4 images mounted read-only. |
| LZMA single-read correctness | PASS | 4 KiB, 16 KiB, 64 KiB and 1 MiB single `pread()` calls returned the requested bytes with `errno=0`; every output range matched the source range SHA-256. |
| LZMA full-file correctness | PASS | Cold and warm full SHA-256 runs returned zero and matched the 8 MiB source hash. |
| LZMA liveness and performance | PASS | Cold full SHA completed in 2.16 seconds and warm full SHA in 1.44 seconds, both below the 120-second host hard limit. |
| LZMA concurrency | PASS | Two simultaneous full SHA processes both returned zero and the expected hash; guest real times were 22.52 and 21.97 seconds. |
| Non-LZMA regression | PASS | The 8 MiB LZ4 fixture returned the expected SHA-256 in 9.03 seconds. |
| Cleanup and guard integrity | PASS | Test QEMU PID 812999 exited, port 10030 closed, its overlay was deleted, and guard PID 26318/port 9222 remained alive and reachable. |
Overall verdict: **PASS for the required final manual smoke scope**.
## Single `pread()` evidence
Each probe program performs one target-file `pread()`. Truss confirmed one
target call at offset zero for every requested length. Dynamic-loader reads are
not counted.
| Length | Returned | Probe command wall time | Output/source range hash | Verdict |
| ---: | ---: | ---: | --- | --- |
| 4 KiB | 4 KiB | 2.696 s | `1c3c59331a4849514667bbc51c078327577b98f75a62996bbc2fe310b014dd79` | PASS |
| 16 KiB | 16 KiB | 2.140 s | `2433ce7c2ea151f487a319447ce4fe14e1a2c2af9f0e5113fe47fc74b6138eee` | PASS |
| 64 KiB | 64 KiB | 1.369 s | `2bb87afd3b9fab420cbe55b782d69a7342150e8816a35efacf22d2ab3012815d` | PASS |
| 1 MiB | 1 MiB | 3.838 s | `52e806509e5dfeb523904f167fca765d001ea749351ecf51738e61a5352a1ba3` | PASS |
These wall times include SSH and truss overhead and are not pure kernel read
times.
## Full-file SHA evidence
The 8 MiB LZMA source hash is:
`ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`
| Run | Exit | Guest real time | Captured hash | Verdict |
| --- | ---: | ---: | --- | --- |
| Cold, fresh mount | 0 | 2.16 s | expected hash | PASS |
| Warm, same mount | 0 | 1.44 s | expected hash | PASS |
| Concurrent process 1 | 0 | 22.52 s | expected hash | PASS |
| Concurrent process 2 | 0 | 21.97 s | expected hash | PASS |
The concurrency result shows substantial contention compared with a single
reader, but it completed correctly and stayed well inside the 120-second hard
limit. This run does not claim concurrency performance is optimized.
## Baseline comparison
The preceding controlled Pre7/Pre8/Pre9 run sampled each full SHA after about
eight seconds. Each process was still inside the LZMA decompression path and
had advanced only about 1.0-1.25 MiB; no final hash was captured. That report
therefore classified full-read liveness as failed and correctness as blocked.
With the mount-scoped decoded extent cache, the same 8 MiB fixture completed
with the correct hash in 2.16 seconds cold and 1.44 seconds warm. This closes
the previous full-file correctness block for this fixture and demonstrates a
material liveness improvement. It does not establish a precise speedup ratio,
because the baseline process was terminated at the observation point rather
than allowed to finish.
## Non-LZMA result
The LZ4 compact-64k image contained `compressed.bin` with expected SHA-256:
`3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880`
The mounted file produced the same hash, returned zero and completed in 9.03
seconds of guest real time.
## Kernel and cleanup observations
No EROFS panic, assertion, mount error or decompression error was found in the
captured dmesg tail. The image emitted pre-existing root-filesystem directory
warnings during boot; they occurred before the test KLD was loaded and are not
attributed to EROFS.
All test-specific mounts and md providers were explicitly detached before VM
shutdown. A final generic cleanup command contained an awk quoting error, but
it ran only after the explicit LZMA and LZ4 unmount/md detach operations had
already returned zero. Host-side cleanup independently confirmed:
- PID 812999 absent;
- port 10030 closed;
- test overlay absent;
- guard PID 26318 alive;
- guard port 9222 open.
## Uncovered cases
This is a targeted final smoke validation, not a full feature suite. It did not
exercise partial-reference LZMA, metadata tailpacking, fragments, multi-device
images, forced unmount under active I/O, repeated mount/unmount under memory
pressure, or deliberate allocation failure. Those remain separate regression
and stress-test work.
Concise machine-readable evidence is stored in
`docs/pre9-manual-evidence/pre9-cache-final-result.json`. Target syscall lines
are stored in `docs/pre9-manual-evidence/pre9-cache-final-probes.txt`.
+158
View File
@@ -0,0 +1,158 @@
# Pre9 controlled manual QEMU evidence audit
Date: 2026-08-13
## Scope and evidence status
This report audits the already completed manual run at
`/work/tests-dev/temp/pre9-manual-20260813-run3/`. No QEMU test was rerun.
The long-lived guard VM (PID 26318, host port 9222) was not touched.
The automated TC004 implementation remains `PENDING_FIX/BLOCKED` in
`/work/tests-dev/fix-todo/tc004-automation.md`. Run3 is a manual substitute;
it is not evidence that TC004 automation passes.
Run history:
- Run1: infrastructure failure; no retained result directory is available.
- Run2: `INFRA`. Guest SSH authentication failed for Pre7 and Pre8, then the
host runner was interrupted while preparing Pre9. It produced no DUT result.
- Run3: valid manual run. All three DUTs built, loaded, mounted, executed the
bounded probes, produced a live SHA stack sample, and cleaned up.
## Verdict
| Question | Verdict | Evidence |
| --- | --- | --- |
| 4 KiB, 16 KiB, 64 KiB and 1 MiB single-syscall read correctness | PASS | Every version returned the requested byte count, `errno=0`, matched the source-range SHA-256, exited zero, and has exactly one target-file `pread()` in truss. |
| First 1 MiB sequential `dd` read | PASS | Every version returned zero and copied 1 MiB within the 20-second bound. This is a bounded smoke check, not proof of one 1 MiB kernel read. |
| Full-file SHA correctness | BLOCKED | No version retained a hash or an exit status for the background SHA. `sha_after` is empty and process disappearance is not correctness evidence. |
| Full-file read liveness/performance | FAIL | After an eight-second observation delay, SHA was still running in the LZMA decode path and had advanced only about 1.0-1.25 MiB. This is unacceptable for the 8 MiB smoke fixture and reproduces across all three versions. |
| Pre7 to Pre8/Pre9 regression attribution | PASS: no Pre9 regression demonstrated | Pre8 and Pre9 have the same `src` tree and identical KLD hash. Pre7 has a different source tree/KLD but shows the same symptom and stack. The evidence attributes the issue to shared behavior, not a Pre9-only change. |
Overall verdict: **PARTIAL**. Bounded reads are correct, but the full-file
correctness verdict is blocked and full-file liveness fails.
## Runner semantics audit
The runner creates a fresh qcow2 overlay per version over the read-only base,
archives the selected repository subtree, builds the KLD in the guest, mounts
the same LZMA fixture, and runs four probes. Each probe invokes a C program that
contains one target-file `pread()` and writes the returned bytes to a file.
The runner then hashes that file and compares it with bytes read from the
uncompressed source fixture.
Important field meanings:
- `commit`: Git tree object for the version directory at the enclosing repo
HEAD. It is not a standalone commit ID.
- `src_tree`: Git tree object for that version's `src` directory.
- `probes[].elapsed`: host wall time for the complete SSH command, including
SSH and truss overhead. It is not pure kernel decompression time.
- `metadata.hash_match`: equality between the mounted output-range hash and
the uncompressed source-range hash.
- `signal=0`: runner shorthand for probe return code zero. It is not a signal
collected through `waitpid()`.
- `dd_1m`: a userspace `dd bs=1m count=1` result. It does not establish the
size or count of VOP/kernel reads.
- `sha_pid`: PID printed after starting a background `sha256` command.
- `diagnostic_sample`: process table, kernel stack, descriptor offset, mount,
md device and dmesg captured after an unconditional eight-second sleep.
- `sha_after`: process status followed by `sha.out` and `sha.err`. Empty output
means neither a process row nor captured SHA/error output was available.
- `status=PARTIAL`: runner-generated fallback when any bounded probe succeeds.
It does not mean full SHA correctness passed.
The full SHA was not run under `timeout`. The runner waited eight seconds,
sampled it, sent TERM, slept two seconds, then attempted KILL, and finally read
the output files. No start/end timestamp or exit status was recorded. Therefore
the exact SHA lifetime is unknown; the only defensible timing statement is that
it was still active approximately eight seconds after launch.
`sha_after` contains only `,state=,command=` for all versions. The subsequent
KILL reports `No such process`. This establishes only that the sampled PID no
longer existed after TERM plus the two-second delay. It does not distinguish a
successful completion from TERM handling, and the absent `sha.out` means no
hash can be validated. Process disappearance must not be reported as PASS.
## Controlled identities and setup
The common fixture hashes were:
- Image: `32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9`
- Source: `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`
| Version | Repository tree | `src` tree | KLD SHA-256 | Build/load/mount |
| --- | --- | --- | --- | --- |
| Pre7 | `b8f9af2cd55225c4348b79ff5910ae6fc83cd517` | `dc203534d7b5721905f8538026e4c59346106020` | `33a52f2f16a94afda0501d305b238678b96c71e420d4b8bbcbeec6ffcdf1aae5` | PASS/PASS/PASS |
| Pre8 | `1e24c992a5b071e6fdabfdc42d342b6ed5a91cf5` | `cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2` | `348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0` | PASS/PASS/PASS |
| Pre9 | `dc0c01157b5c925684bd77c57ed6703904af7453` | `cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2` | `348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0` | PASS/PASS/PASS |
Pre8 and Pre9 are runtime-identical for this test according to both the source
tree and the produced KLD hash.
## Single-syscall probes
All probes used offset zero. Times below include SSH/truss overhead.
| Version | Length | Returned | Hash match | Elapsed | Exactly one target `pread()` |
| --- | ---: | ---: | --- | ---: | --- |
| Pre7 | 4 KiB | 4 KiB | yes | 1.299 s | yes |
| Pre7 | 16 KiB | 16 KiB | yes | 1.291 s | yes |
| Pre7 | 64 KiB | 64 KiB | yes | 1.438 s | yes |
| Pre7 | 1 MiB | 1 MiB | yes | 1.432 s | yes |
| Pre8 | 4 KiB | 4 KiB | yes | 1.216 s | yes |
| Pre8 | 16 KiB | 16 KiB | yes | 1.327 s | yes |
| Pre8 | 64 KiB | 64 KiB | yes | 1.430 s | yes |
| Pre8 | 1 MiB | 1 MiB | yes | 1.442 s | yes |
| Pre9 | 4 KiB | 4 KiB | yes | 1.791 s | yes |
| Pre9 | 16 KiB | 16 KiB | yes | 1.341 s | yes |
| Pre9 | 64 KiB | 64 KiB | yes | 1.374 s | yes |
| Pre9 | 1 MiB | 1 MiB | yes | 1.737 s | yes |
The target-file truss lines are preserved in
`pre9-manual-evidence/probe-summary.txt`. Dynamic-loader `pread()` calls are
not counted as target-file calls.
The 1 MiB `dd` results were:
- Pre7: 1 MiB in 0.195526 s.
- Pre8: 1 MiB in 0.179484 s.
- Pre9: 1 MiB in 0.367417 s.
These values are not stable enough for version performance ranking, but all
three bounded operations completed.
## Full SHA sample
| Version | State at sample | File offset | Kernel stack | Final hash |
| --- | --- | ---: | --- | --- |
| Pre7 | running (`RC`) | 1,245,184 | `lzma2_lzma -> erofs_xz_dec_microlzma_run -> lzma_decompress -> z_erofs_decompress -> z_erofs_do_read -> z_erofs_read_uio` | absent |
| Pre8 | running (`RC`) | 1,150,976 | same | absent |
| Pre9 | running (`RC`) | 1,044,480 | same | absent |
The stack is direct evidence of active CPU-side decompression, not an EROFS
lock wait. It does not by itself prove why decompression is slow. Combined with
the static review, the leading explanation remains repeated full mapped-extent
decompression as `z_erofs_read_uio()` segments reads at `MAXPHYS`. There is no
direct evidence here of an XZ infinite loop or an EROFS lock deadlock.
## Infrastructure integrity and cleanup
The base image SHA-256 before and after run3 was identical and matched the
expected value:
`67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef`
For each version, `umount`, `mdconfig -d` and `kldunload` returned zero. Each
QEMU PID was no longer alive and each overlay was deleted. The run3 result set
therefore supports successful test-VM cleanup. This report does not infer the
state of the unrelated long-lived guard VM and did not inspect or modify it.
## Required follow-up
A supplemental manual test is still required if full-file correctness must be
closed. It should run one full SHA with an explicit wall-clock deadline, record
start/end timestamps and exit status, capture `sha.out` before cleanup, compare
the hash to the source fixture, and separately record whether TERM/KILL was
used. Until then, full-file SHA correctness remains `BLOCKED`.
+142
View File
@@ -0,0 +1,142 @@
# Pre9 LZMA decoded extent cache
## Decision
Pre7, Pre8, and Pre9 all reproduced the same LZMA read-liveness symptom. The
manual evidence shows that a single large read completes quickly while a full
file read made up of successive small reads remains in the LZMA decode path.
The source path creates and destroys a decoded extent for every mapped read;
the existing FreeBSD buffer cache only retains compressed block buffers.
A cache local to `z_erofs_read_uio()` would not cross the VOP/read boundary
between successive small user reads. Commit `61f4709` therefore kept one
decoded extent on each compressed-file vnode. Review found that retained
memory could then grow with the number of open vnodes. This follow-up moves
the cache to `struct erofs_mount`: each mount retains at most one decoded
extent until unmount, so ordinary open-file count cannot expand the cache.
A single entry can thrash between files and concurrent readers; that is an
accepted first-stage tradeoff.
The cache is deliberately limited to complete, non-partial MicroLZMA extents.
Partial references keep the existing path because their decoded length and
reference semantics are different. Other codecs are also unchanged in this
phase: the measured failure is LZMA-specific, and broadening the change would
make the validation and regression attribution less precise.
## Data and lifecycle
`struct erofs_zextent_cache` stores the decoded allocation and the mapping
identity needed to prove that it can be reused:
```c
struct erofs_zextent_cache {
void *data;
erofs_nid_t m_nid;
erofs_off_t m_pa;
erofs_off_t m_la;
uint64_t m_plen;
uint64_t m_llen;
unsigned int m_deviceid;
unsigned int m_flags;
unsigned char m_algorithmformat;
};
```
The cache mutex is initialized immediately after allocating
`struct erofs_mount`. Every mount failure path reaches `erofs_sb_free()`, and
normal unmount calls the same helper after `vflush()`. There is no cache field
or cache lifecycle dependency in `struct erofs_node`. The cache key includes
the inode NID because one mount entry is shared by all regular file vnodes.
NID zero remains a valid key; `data != NULL` is the validity bit.
EROFS is read-only, so a decoded extent does not need write invalidation.
`vflush()` completes before unmount frees the mount object, and
`z_erofs_extent_cache_fini()` releases the single mount-owned allocation.
`EROFS_MAP_META` is explicitly excluded, so metadata-backed tailpacking keeps
the previous path. `packed_inode` and `metabox_en` are also excluded because
they are mount-private backing objects, not user file vnodes.
## Concurrency
Decompression and block reads occur without holding `z_extent_cache_lock`. A reader
first takes the lock only to compare the complete key and copy a cache hit.
On a miss, it builds a private decoded extent. It then takes the lock again:
1. If another reader published the same key, copy that published extent and
discard the duplicate private allocation.
2. Otherwise replace the one cached extent, copy the requested range, unlock,
and free the old allocation.
This permits duplicate construction under concurrent misses but keeps all
shared pointer access under the mutex. The lock is never held across
`bread()`, allocation, or decompression, all of which may sleep. The cache
allocation is never freed while a reader is copying it because replacement,
hit copying, and pointer clearing are serialized by the same mutex.
The helpers are reached only when `want <= MAXPHYS` and contain `KASSERT`
checks for that contract. Thus the largest lock-held copy is the FreeBSD
`MAXPHYS` request size, not the 12 MiB on-disk extent cap. Calls that can pass
more than `MAXPHYS` use the existing uncached copy path. This bounded mutex
copy is accepted for the first stage; a refcounted immutable entry is deferred
until runtime evidence shows that this copy is materially contended.
## Bounds and unchanged paths
The existing mapping sanity checks cap a mapped compressed extent at
`Z_EROFS_PCLUSTER_MAX_DSIZE` before the read path. `z_erofs_read_extent()` also
checks the compressed and decoded lengths before allocation. The read path now
explicitly checks `mapoff` and the extent length before converting them to
`size_t`; this protects the cache offset arithmetic on platforms where
`size_t` is narrower than the on-disk fields.
Cache use requires all of the following:
```text
compressed mapped extent
not EROFS_MAP_PARTIAL_REF
Z_EROFS_COMPRESSION_LZMA
initialized mount cache
not `EROFS_MAP_META`
not a mount-private backing inode
request length no greater than `MAXPHYS`
```
Fragments, holes, partial references, non-LZMA codecs, uncompressed files,
metadata reads, and mount-private backing inodes retain their previous code
paths and error handling.
## Rejected alternatives for Pre9
- A function-local cache was rejected because it cannot span successive VOP
reads that caused the observed amplification.
- A per-vnode cache was rejected after the `61f4709` review: open file count
could retain one decoded extent per vnode without a system-wide bound.
- A larger cross-vnode cache was rejected because it would require an eviction
policy and larger memory accounting. The one-entry per-mount cache is the
controlled compromise: it has a fixed mount-scoped bound and simple teardown.
- A Linux page/folio/XArray/workqueue port was rejected because those are not
FreeBSD vnode/buf primitives and would create an unnecessary compatibility
layer.
- A decoder stream pool was deferred: it may reduce allocator overhead but
does not remove repeated full extent decompression.
- Changing `MAXPHYS`, changing the disk format, bypassing the buffer cache, or
adding decoder retries was rejected because none addresses the demonstrated
decoded-result reuse and each changes unrelated behavior or resource bounds.
## Static validation and required runtime matrix
This change is static-only in the source phase. The follow-up test agent must
run the unchanged LZMA fixture against Pre9 and require:
1. bounded single reads at 4 KiB, 16 KiB, 64 KiB, and 1 MiB with source-range
hash equality;
2. repeated small reads spanning the same extent, with completion and hash
equality;
3. complete sequential SHA-256 with recorded exit status and elapsed time;
4. concurrent reads of the same file and close/reopen reads;
5. partial-reference, plain, LZ4, DEFLATE, and ZSTD regression coverage;
6. clean unmount, md detach, and module unload after every case.
The automation verdict and DUT verdict must remain separate. A timeout or
missing final hash remains a failure or blocked result; it must not be promoted
to PASS because bounded reads succeed.
+62
View File
@@ -0,0 +1,62 @@
# Pre9 LZMA cache review resolution
This document records the follow-up to commit `61f4709`, which cached one
decoded LZMA extent per vnode.
## Findings addressed
- The cache is now one entry in `struct erofs_mount`, rather than one entry in
every `struct erofs_node`. Retained decoded memory is bounded by one extent
per mounted filesystem, with the number of mounts controlled by mount
privileges. Keeping many ordinary file descriptors open cannot create more
cache entries.
- Cache initialization is performed in `erofs_mountfs()` immediately after
mount allocation. `erofs_sb_free()` destroys it on every mount failure path
and after successful `vflush()` during unmount. Node reclaim no longer owns
cache cleanup.
- `EROFS_MAP_META` is an explicit ineligibility condition. Metadata-backed
tailpacking therefore remains on its existing read path.
- `packed_inode` and `metabox_en` are explicit ineligibility conditions. The
mount-private backing objects cannot populate or consume the user-data
cache.
- The cache type and helper names are now `erofs_zextent_cache` and
`z_erofs_extent_cache_*`, distinguishing decoded extents from Linux's
managed compressed-page cache names.
- The key retains the existing physical/logical extent fields, device id,
flags, and algorithm, and adds the stable inode `nid`. The `data` pointer is
the validity bit, so NID zero is not treated as an empty key.
## Copy bound and concurrency
`z_erofs_read_uio()` limits each output request to `MAXPHYS`. The generic
`z_erofs_read_data()` API can receive a larger request, so cache eligibility
rejects any request whose mapped portion exceeds `MAXPHYS`. Both cache helper
entry points also contain `KASSERT(len <= MAXPHYS)` checks. The largest copy
performed while holding `z_extent_cache_lock` is therefore `MAXPHYS`; the
12 MiB `Z_EROFS_PCLUSTER_MAX_DSIZE` limit remains an on-disk decoded-extent
allocation bound, not a mutex-copy bound.
Allocation, compressed reads, and decompression happen outside the mutex. A
cache hit copies while holding the mutex, and a miss publishes and copies
under the same mutex before the previous allocation is freed. Concurrent
misses may decode duplicate extents and may replace one another, but pointer
access and replacement remain serialized. A refcounted immutable cache entry
was deliberately not introduced in this first stage.
## Resource tradeoff
The per-mount entry is intentionally a small first-stage design. It removes
the unbounded-per-open-vnode retention introduced by `61f4709`, but it can
thrash when many files are read concurrently on one mount. Retention lasts
until unmount, and the retained allocation is capped by the existing EROFS
format constant `Z_EROFS_PCLUSTER_MAX_DSIZE` (12 MiB). A FreeBSD shrinker or
pressure callback is deferred; adding one would require a broader memory
accounting and lifecycle design than this corrective commit.
## Validation scope
This correction is statically validated only. QEMU validation remains
required for complete SHA-256 reads, same-file concurrent reads, close/reopen,
unmount cleanup, metadata tailpacking, and memory-pressure behavior. The
existing Pre9 manual test report predates this correction and must not be
reported as runtime validation of this commit.
@@ -0,0 +1,26 @@
Run3 full-file SHA samples, captured approximately eight seconds after launch
repo-pre-7
state: RC
file offset: 1245184
kernel stack: lzma2_lzma -> erofs_xz_dec_microlzma_run -> lzma_decompress -> z_erofs_decompress -> z_erofs_do_read -> z_erofs_read_uio -> VOP_READ_APV -> vn_read
sha_after: no process row, no sha.out hash, no sha.err content
termination: TERM sent; KILL then reported No such process
repo-pre-8
state: RC
file offset: 1150976
kernel stack: lzma2_lzma -> erofs_xz_dec_microlzma_run -> lzma_decompress -> z_erofs_decompress -> z_erofs_do_read -> z_erofs_read_uio -> VOP_READ_APV -> vn_read
sha_after: no process row, no sha.out hash, no sha.err content
termination: TERM sent; KILL then reported No such process
repo-pre-9
state: RC
file offset: 1044480
kernel stack: lzma2_lzma -> erofs_xz_dec_microlzma_run -> lzma_decompress -> z_erofs_decompress -> z_erofs_do_read -> z_erofs_read_uio -> VOP_READ_APV -> vn_read
sha_after: no process row, no sha.out hash, no sha.err content
termination: TERM sent; KILL then reported No such process
Interpretation: the SHA processes were alive and executing in LZMA decode at
the sample. Their later disappearance does not prove successful completion.
No full-file hash was captured, so full-file correctness remains BLOCKED.
@@ -0,0 +1,16 @@
Pre9 final decoded extent cache kernel and cleanup summary
Date: 2026-08-13
- Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG, 6144 MiB, 4 vCPUs.
- KLD built with WITH_ZSTDIO=0 and loaded successfully.
- No EROFS panic, assertion, mount error or decompression error appeared in
the captured dmesg tail.
- Boot emitted pre-existing root-filesystem "bad dir ino" warnings before
the test KLD was loaded; these are not attributed to EROFS.
- Explicit LZMA probe, LZMA full-read and LZ4 unmount/md detach operations
returned zero.
- Test QEMU PID 812999 was terminated.
- Host port 10030 was closed.
- Test overlay was removed.
- Guard QEMU PID 26318 remained alive.
- Guard SSH port 9222 remained open.
@@ -0,0 +1,21 @@
Pre9 final decoded extent cache target-file syscall evidence
Date: 2026-08-13
4 KiB:
pread(3, ..., 4096, 0x0) = 4096 (0x1000)
output/source SHA-256: 1c3c59331a4849514667bbc51c078327577b98f75a62996bbc2fe310b014dd79
16 KiB:
pread(3, ..., 16384, 0x0) = 16384 (0x4000)
output/source SHA-256: 2433ce7c2ea151f487a319447ce4fe14e1a2c2af9f0e5113fe47fc74b6138eee
64 KiB:
pread(3, ..., 65536, 0x0) = 65536 (0x10000)
output/source SHA-256: 2bb87afd3b9fab420cbe55b782d69a7342150e8816a35efacf22d2ab3012815d
1 MiB:
pread(3, ..., 1048576, 0x0) = 1048576 (0x100000)
output/source SHA-256: 52e806509e5dfeb523904f167fca765d001ea749351ecf51738e61a5352a1ba3
Each trace contained exactly one pread() against the mounted target file.
Dynamic-loader pread() calls were excluded from this summary.
@@ -0,0 +1,100 @@
{
"archive_sha256": "a6a6d573ace42fe713529974c7bd20b472a1e4521920fac40e01c5d3bb071bcb",
"cleanup": {
"guard_pid_alive": true,
"guard_port_9222_open": true,
"overlay_exists": false,
"port_10030_open": false,
"qemu_pid_alive": false
},
"finished_utc": "2026-08-13T06:06:25Z",
"kld_sha256": "473a6205f43905972f2417609d43f67ab2cb51ea79bc1716fffa5c1914ff85af",
"lz4_sha": {
"expected_sha256": "3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880",
"guest_real_seconds": 9.03,
"hash_match": true,
"returncode": 0,
"sha256": "3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880"
},
"lzma_concurrent": {
"guest_real_seconds": [
22.52,
21.97
],
"hash_match": true,
"hashes": [
"ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461",
"ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461"
],
"host_timeout": false,
"returncode": 0,
"returncodes": [
0,
0
]
},
"lzma_fixture": {
"image_sha256": "32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9",
"source_sha256": "ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461"
},
"lzma_sha": {
"cold": {
"guest_real_seconds": 2.16,
"hash_match": true,
"host_timeout": false,
"returncode": 0,
"sha256": "ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461"
},
"warm": {
"guest_real_seconds": 1.44,
"hash_match": true,
"host_timeout": false,
"returncode": 0,
"sha256": "ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461"
}
},
"repo_pre_9_tree": "9d7eaf63a1c5d07320af3ef39930fc0a3530dd11",
"schema": "pre9-cache-final-manual-v1",
"single_pread": [
{
"elapsed_seconds": 2.696,
"hash_match": true,
"length": 4096,
"returncode": 0,
"sha256": "1c3c59331a4849514667bbc51c078327577b98f75a62996bbc2fe310b014dd79"
},
{
"elapsed_seconds": 2.14,
"hash_match": true,
"length": 16384,
"returncode": 0,
"sha256": "2433ce7c2ea151f487a319447ce4fe14e1a2c2af9f0e5113fe47fc74b6138eee"
},
{
"elapsed_seconds": 1.369,
"hash_match": true,
"length": 65536,
"returncode": 0,
"sha256": "2bb87afd3b9fab420cbe55b782d69a7342150e8816a35efacf22d2ab3012815d"
},
{
"elapsed_seconds": 3.838,
"hash_match": true,
"length": 1048576,
"returncode": 0,
"sha256": "52e806509e5dfeb523904f167fca765d001ea749351ecf51738e61a5352a1ba3"
}
],
"src_tree": "e657e8a63b097b061670f75ca01947a568ef33d5",
"started_utc": "2026-08-13T05:59:41Z",
"tested_head": "cdcf276d12169a672d2de42996532a470ac061d9",
"verdict": {
"build_load_mount": "PASS",
"cleanup": "PASS",
"lzma_concurrency": "PASS",
"lzma_full_correctness": "PASS",
"lzma_liveness_performance": "PASS",
"lzma_single_pread_correctness": "PASS",
"non_lzma_lz4": "PASS"
}
}
@@ -0,0 +1,22 @@
Run3 target-file syscall evidence (dynamic-loader pread calls omitted)
repo-pre-7
4096: pread(fd, ..., 4096, 0) = 4096; process exit 0
16384: pread(fd, ..., 16384, 0) = 16384; process exit 0
65536: pread(fd, ..., 65536, 0) = 65536; process exit 0
1048576: pread(fd, ..., 1048576, 0) = 1048576; process exit 0
repo-pre-8
4096: pread(fd, ..., 4096, 0) = 4096; process exit 0
16384: pread(fd, ..., 16384, 0) = 16384; process exit 0
65536: pread(fd, ..., 65536, 0) = 65536; process exit 0
1048576: pread(fd, ..., 1048576, 0) = 1048576; process exit 0
repo-pre-9
4096: pread(fd, ..., 4096, 0) = 4096; process exit 0
16384: pread(fd, ..., 16384, 0) = 16384; process exit 0
65536: pread(fd, ..., 65536, 0) = 65536; process exit 0
1048576: pread(fd, ..., 1048576, 0) = 1048576; process exit 0
All mounted-output hashes matched corresponding source-range hashes. The full
hash values and elapsed times are retained in run3-audit.json.
+62
View File
@@ -0,0 +1,62 @@
{
"base_image": {
"before": "67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef",
"after": "67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef",
"expected": "67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef",
"unchanged": true
},
"fixture": {
"image_sha256": "32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9",
"source_sha256": "ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461"
},
"versions": [
{
"name": "repo-pre-7",
"repository_tree": "b8f9af2cd55225c4348b79ff5910ae6fc83cd517",
"src_tree": "dc203534d7b5721905f8538026e4c59346106020",
"kld_sha256": "33a52f2f16a94afda0501d305b238678b96c71e420d4b8bbcbeec6ffcdf1aae5",
"probes": [
{"length": 4096, "returned": 4096, "elapsed_seconds": 1.2993653398007154, "hash_match": true, "target_pread_count": 1},
{"length": 16384, "returned": 16384, "elapsed_seconds": 1.2910558842122555, "hash_match": true, "target_pread_count": 1},
{"length": 65536, "returned": 65536, "elapsed_seconds": 1.4380157887935638, "hash_match": true, "target_pread_count": 1},
{"length": 1048576, "returned": 1048576, "elapsed_seconds": 1.4320225361734629, "hash_match": true, "target_pread_count": 1}
],
"dd_1m_seconds": 0.195526,
"sha_sample_offset": 1245184,
"sha_hash_captured": false,
"cleanup": {"umount": 0, "mdconfig_detach": 0, "kldunload": 0, "qemu_alive": false, "overlay_exists": false}
},
{
"name": "repo-pre-8",
"repository_tree": "1e24c992a5b071e6fdabfdc42d342b6ed5a91cf5",
"src_tree": "cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2",
"kld_sha256": "348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0",
"probes": [
{"length": 4096, "returned": 4096, "elapsed_seconds": 1.2161498833447695, "hash_match": true, "target_pread_count": 1},
{"length": 16384, "returned": 16384, "elapsed_seconds": 1.3266378343105316, "hash_match": true, "target_pread_count": 1},
{"length": 65536, "returned": 65536, "elapsed_seconds": 1.4296557288616896, "hash_match": true, "target_pread_count": 1},
{"length": 1048576, "returned": 1048576, "elapsed_seconds": 1.4422911144793034, "hash_match": true, "target_pread_count": 1}
],
"dd_1m_seconds": 0.179484,
"sha_sample_offset": 1150976,
"sha_hash_captured": false,
"cleanup": {"umount": 0, "mdconfig_detach": 0, "kldunload": 0, "qemu_alive": false, "overlay_exists": false}
},
{
"name": "repo-pre-9",
"repository_tree": "dc0c01157b5c925684bd77c57ed6703904af7453",
"src_tree": "cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2",
"kld_sha256": "348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0",
"probes": [
{"length": 4096, "returned": 4096, "elapsed_seconds": 1.7912541721016169, "hash_match": true, "target_pread_count": 1},
{"length": 16384, "returned": 16384, "elapsed_seconds": 1.341409295797348, "hash_match": true, "target_pread_count": 1},
{"length": 65536, "returned": 65536, "elapsed_seconds": 1.3740410972386599, "hash_match": true, "target_pread_count": 1},
{"length": 1048576, "returned": 1048576, "elapsed_seconds": 1.737462431192398, "hash_match": true, "target_pread_count": 1}
],
"dd_1m_seconds": 0.367417,
"sha_sample_offset": 1044480,
"sha_hash_captured": false,
"cleanup": {"umount": 0, "mdconfig_detach": 0, "kldunload": 0, "qemu_alive": false, "overlay_exists": false}
}
]
}
+37
View File
@@ -0,0 +1,37 @@
# repo22 Linux/FreeBSD 结构对齐维护计划
## 基线
- FreeBSD 实现:`src/`
- Linux 参考:`/work/dev-src-linux/fs/erofs`Linux 7.1-rc1 快照)
- 比较方法:逐文件和逐职责比较,不依赖共同 Git 历史
- 运行目标:FreeBSD 15 amd64
## 决策规则
1. 先判断差异是否来自 FreeBSD vnode、GEOM、errno、锁或内核库接口。
2. 只对非必要差异调整静态函数名、变量名、定义顺序和文件职责。
3. 不为匹配 Linux 文件表而添加空模块或不适用的抽象。
4. `src/Makefile` 是模块名、源码清单、架构门控和编译选项的权威来源。
5. 构建产物、生成头和机器 include symlink 不进入版本控制。
## 本轮清单
- [x] 清除受跟踪的 `build/obj` 生成头和机器 symlink
- [x] 精确忽略模块构建及手工测试生成目录
- [x] 由原生 `bsd.kmod.mk` 取代手写 amd64 clang/link 流程
- [x] 明确拒绝未验证的非 amd64 架构
- [x] 将 ZSTDIO 配置统一为 `WITH_ZSTDIO=0/1`
- [x] 按 Linux 顺序整理 Makefile 源码列表
- [x] 对齐 `find_target_dirent``decompressor_*.c` 名称
- [x] 将跨文件解压后端声明集中到 `internal.h`
- [x] 保留 BSD 专属 `erofs_vnops.c``lz4.c` 和后端调用契约
- [x] 在 FreeBSD 15 VM 完成双配置构建和 ZSTD 挂载 smoke
## 验证门槛
1. `WITH_ZSTDIO=0``WITH_ZSTDIO=1` 均从空对象目录构建。
2. 禁用产物不含 `ZSTD_*` 未解析符号,启用产物必须含预期内核 API。
3. 启用产物完成加载、ZSTD 镜像挂载、文件读取、卸载和 KLD 清理。
4. 非 `amd64` 和非法 `WITH_ZSTDIO` 值必须明确失败。
5. 提交只包含 repo22 pathspec,且远端 `xdm/main` 与本地 HEAD 一致。
+40
View File
@@ -0,0 +1,40 @@
# repo22 Linux/FreeBSD 结构对齐状态
## 已完成
- 使用 `/work/dev-src-linux/fs/erofs` 的 Linux 7.1-rc1 快照逐文件复核;未使用
两个实现共享历史的假设。
- `namei.c` 的静态 helper 已从 `erofs_find_target_dirent` 恢复为 Linux 名称
`find_target_dirent`
- `lzma.c``deflate.c``zstd.c` 已按 Linux 职责名调整为
`decompressor_lzma.c``decompressor_deflate.c`
`decompressor_zstd.c`
- 解压后端原型已移入 `internal.h`MicroLZMA 内嵌 XZ 的 allocator helper
已限制为编译单元内部符号。
- `src/Makefile` 已按 Linux 的 metadata、压缩调度/映射、算法后端顺序组织,
并成为 `build.sh` 的唯一源码和模块名来源。
- 受跟踪的对象目录、vnode 生成头和 amd64/x86 机器 symlink 已删除并忽略。
## 保留差异
- `erofs_vnops.c` 承载 FreeBSD vnode、pager、NFS 和只读操作语义。
- `lz4.c` 保持独立,因为 BSD 后端不使用 Linux page/LZ4 调度接口。
- 后端函数保持 BSD 内部 API,而不是复制 Linux decompressor descriptor 和
page 生命周期。
- FreeBSD 路径返回正 errno,并保留 GEOM/provider、lockmgr 和 vnode 规则。
- Linux `sysfs`、file-backed、fscache、page-cache sharing 和 `zutil` 职责不以
空文件模拟。
- 当前构建门控仅允许已验证的 FreeBSD 15 `amd64`
## 2026-08-09 验证
- VMFreeBSD `15.0-RELEASE-p8` amd64。
- 构建源:FreeBSD `15.0 RELEASE-p9` `sys` 树。
- `WITH_ZSTDIO=0` 构建通过,模块 SHA256
`d11d0319dc1d786eaa868a0c05c2abaf43da8480acacb0e1e4b72b7542b05432`
`ZSTD_*` 未解析符号。
- `WITH_ZSTDIO=1` 构建通过,模块 SHA256
`82471f19812e9877ea06901d1ba0cb4378b2bc6476ec70f6e024e0dc777c0c71`
编译命令包含 `-DZSTDIO` 并引用 FreeBSD 内核 ZSTD API。
- 启用模块完成加载、`zstd-level1.erofs` 只读挂载、6 个文件读取与 SHA256、
卸载、md detach 和 KLD unload;测试后无遗留挂载或模块。