EROFS for FreeBSD
Build Kernel Module
Build on FreeBSD 15 amd64 with a matching FreeBSD source tree. The default
source path is /usr/src:
WITH_ZSTDIO=0 ./build.sh
Use FREEBSD_SRC=/path/to/freebsd-src when the source tree is elsewhere. The
script is a native FreeBSD wrapper around src/Makefile and bsd.kmod.mk; the
Makefile is the authoritative module name, source list, architecture gate, and
per-source flag definition. Each run rebuilds build/obj and writes
build/erofs.ko.
Only MACHINE_ARCH=amd64 is currently qualified. Other architectures are
rejected explicitly instead of inheriting amd64 ABI flags. Build ZSTD support
with:
WITH_ZSTDIO=1 ./build.sh
The enabled module references the FreeBSD kernel ZSTD API and therefore
requires a running kernel built with options ZSTDIO. WITH_ZSTDIO=0 builds a
module without those references and rejects ZSTD-compressed images at mount.
Mount
EROFS is read-only. Mount a single-device image with:
mount -t erofs -o ro /dev/md0 /mnt/erofs
For an image with external blob devices, map every one-based on-disk device
slot explicitly with device.<slot>=<provider>:
mount -t erofs -o ro \
-o device.2=/dev/md92 \
-o device.1=/dev/md91 \
/dev/md90 /mnt/erofs
Slot names make the mapping independent of option order. Every external slot
must be present exactly once; assigning the same GEOM provider to multiple
slots is rejected. The same names and values may be passed directly as
nmount(2) iovec entries.
There is no repo-local mount_erofs binary; FreeBSD's generic /sbin/mount
frontend passes these distinct option names through to nmount(2). The driver
forces every successful mount read-only, so even -o rw produces a read-only
mount rather than enabling writes.
If an image has a device table but no device.<slot> options are supplied,
the primary provider is treated as a Linux-compatible flatdev image. It must
contain the external ranges at their declared uniaddr block offsets, for
example a deterministic concatenation of the primary image and its blobs.
Qualified Semantics
- Compact/extended metadata, Linux device-number decode, plain/inline/chunk data, and LZ4/MicroLZMA/DEFLATE/ZSTD compressed reads.
- Inline tails are confined to the inode metadata block and declared image or metabox backing bounds.
- Directory lookup/readdir share strict validation while accepting Linux-style nonzero unused bytes after the final name NUL.
- Compressed
st_blocksreflects the inode's on-disk compressed block count; uncompressed and chunk files retain logical block rounding. - FreeBSD 15 local vnode pager sync/async entry points are used for real mmap faults.
- NFS export uses full 64-bit NIDs and a generation derived from the
superblock seed and inode metadata. Replacing metadata changes the generation
and makes old handles stale; a metadata-identical, payload-only replacement
is not guaranteed to return
ESTALE.
See docs/features.md for the bounded feature claim and
tests/results/manual/2026-08-09T1804Z-final-review-independent/manual-test-report.md
for the final-review evidence.
Test Fixtures
tests/prepare_directory_fixtures.sh creates the deterministic TC048/141/148
directory fixtures. tests/prepare_error_fixtures.sh and
tests/erofs_fixture.py create and self-check the structured TC002/086/087/102
and TC112-TC116/119 fixtures. Generated trees, images, overlays, and build/
outputs are test artifacts and are not committed.