[PATCH v2 experimental-tests] erofs-utils: tests: add test for rebuild fulldata

Lucas Karpinski lkarpinski at nvidia.com
Wed Apr 22 03:04:21 AEST 2026


Add a regression test for building a self-contained image out of two other
erofs images. Validate that image against a live overlayfs mount.

1. Build a base erofs image with deterministic fixtures.
2. Produce two overlayfs upperdirs; generating whitouts and opaque markers.
3. Build an erofs image per layer, then merge all three.
4. Delete the source layers to validate the merged image is self-contained.
5. Mount the merged image and a read-only stacked overlayfs and assert both
have identical checksums.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Lucas Karpinski <lkarpinski at nvidia.com>
---
 tests/Makefile.am   |   3 +
 tests/erofs/030     | 179 ++++++++++++++++++++++++++++++++++++++++++++
 tests/erofs/030.out |   2 +
 3 files changed, 184 insertions(+)
 create mode 100755 tests/erofs/030
 create mode 100644 tests/erofs/030.out

diff --git a/tests/Makefile.am b/tests/Makefile.am
index d8ac0678..125aa8f6 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -126,6 +126,9 @@ TESTS += erofs/028
 # 029 - test FUSE daemon and kernel error handling on corrupted inodes
 TESTS += erofs/029

+# 030 - verify multi-source fulldata rebuild
+TESTS += erofs/030
+
 # NEW TEST CASE HERE
 # TESTS += erofs/999

diff --git a/tests/erofs/030 b/tests/erofs/030
new file mode 100755
index 00000000..afbb69dd
--- /dev/null
+++ b/tests/erofs/030
@@ -0,0 +1,179 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0+
+#
+# verify `mkfs.erofs --clean=data` merges a base image and two
+# overlayfs-derived layers into an image matching a live overlayfs view
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$(echo $0 | awk '{print $((NF-1))"/"$NF}' FS="/")
+
+. "${srcdir}/common/rc"
+
+cleanup()
+{
+	cd /
+	for m in "$ref_mnt" "$ovl2_mnt" "$ovl1_mnt"; do
+		[ -n "$m" ] || continue
+		$UMOUNT_PROG "$m" 2>/dev/null || $UMOUNT_PROG -l "$m" 2>/dev/null
+	done
+	rm -rf $tmp.*
+}
+
+# _mount_overlay_rw <lowers> <upper> <work> <mnt>
+_mount_overlay_rw()
+{
+	$MOUNT_PROG -t overlay overlay \
+		-o lowerdir="$1",upperdir="$2",workdir="$3" "$4" \
+		>>$seqres.full 2>&1 || _fail "failed to mount overlay at $4"
+}
+
+# overlay teardown can race with kernel dentry release; fall back to lazy
+_overlay_unmount()
+{
+	sync
+	$UMOUNT_PROG "$1" >>$seqres.full 2>&1 && return
+	$UMOUNT_PROG -l "$1" >>$seqres.full 2>&1 ||
+		_fail "failed to unmount overlay at $1"
+}
+
+# kernel overlay can uses chrdev(0,0) or trusted.overlay.whiteout=y, but
+# mkfs.erofs only understands the chrdev form, so promote
+_normalize_overlay_whiteouts()
+{
+	local p
+
+	for p in `find "$1" -type f`; do
+		getfattr -n trusted.overlay.whiteout "$p" >/dev/null 2>&1 || continue
+		rm -f "$p" && mknod "$p" c 0 0 >>$seqres.full 2>&1 ||
+			_fail "failed to convert whiteout $p"
+	done
+}
+
+# drop overlay bookkeeping xattrs (origin/impure/uuid/...) that are
+# invisible through a live overlay; keep opaque, --ovlfs-strip=1 handles it
+_strip_overlay_bookkeeping_xattrs()
+{
+	local name p
+
+	for p in `find "$1" -mindepth 1`; do
+		getfattr -d -m 'trusted\.overlay\.' --absolute-names "$p" \
+			2>/dev/null |
+		awk -F= '/^trusted\.overlay\./ {print $1}' |
+		while read name; do
+			[ "$name" = "trusted.overlay.opaque" ] && continue
+			setfattr -x "$name" "$p" >>$seqres.full 2>&1 ||
+				_fail "failed to strip $name from $p"
+		done
+	done
+}
+
+# normalize a kernel-produced upperdir and build an EROFS image from it
+_build_layer_image()
+{
+	local upper="$1" img="$2"
+
+	_normalize_overlay_whiteouts "$upper"
+	_strip_overlay_bookkeeping_xattrs "$upper"
+	$MKFS_EROFS_PROG "$img" "$upper" >>$seqres.full 2>&1 ||
+		_fail "failed to build layer image $img"
+}
+
+_require_erofs
+_require_overlayfs
+_require_fssum
+_require_xattr
+
+rm -f $seqres.full
+echo "QA output created by $seq"
+
+if [ -z "$SCRATCH_DEV" ]; then
+	SCRATCH_DEV=$tmp/erofs_$seq.img
+	rm -f $SCRATCH_DEV
+fi
+
+basedir="$tmp/$seq.base"
+baseimg="$tmp/$seq.base.erofs"
+img1="$tmp/$seq.layer1.erofs"
+img2="$tmp/$seq.layer2.erofs"
+upper1="$tmp/$seq.upper1"; work1="$tmp/$seq.work1"
+upper2="$tmp/$seq.upper2"; work2="$tmp/$seq.work2"
+ovl1_mnt="$tmp/$seq.ovl1.mnt"
+ovl2_mnt="$tmp/$seq.ovl2.mnt"
+ref_mnt="$tmp/$seq.ref.mnt"
+
+mkdir -p "$basedir" "$upper1" "$work1" "$upper2" "$work2" \
+	"$ovl1_mnt" "$ovl2_mnt" "$ref_mnt"
+
+# collect files pending for verification
+dirs=`find ../ -maxdepth 1 -type d -exec printf {}: \;`
+IFS=':'
+for d in $dirs; do
+	[ "$d" = '../' ] && continue
+	[ -z "${d##\.\./tests*}" ] && continue
+	[ -z "${d##\.\./\.*}" ] && continue
+	cp -R "$d" "$basedir"
+done
+unset IFS
+
+# deterministic fixtures for the overlay layers to manipulate
+mkdir -p "$basedir/app/opaque_dir/subdir" "$basedir/app/delete_dir" \
+	"$basedir/app/keep_dir"
+echo "base content" > "$basedir/app/keep_dir/change.txt"
+echo "base whiteout target" > "$basedir/app/keep_dir/remove_l1.txt"
+echo "base layer2 target" > "$basedir/app/keep_dir/remove_l2.txt"
+echo "from base" > "$basedir/app/opaque_dir/subdir/original.txt"
+echo "to be removed" > "$basedir/app/delete_dir/original.txt"
+
+$MKFS_EROFS_PROG "$baseimg" "$basedir" >>$seqres.full 2>&1 ||
+	_fail "failed to create base image"
+
+# layer 1: add dir+file, modify a file, delete a file (whiteout)
+_mount_overlay_rw "$basedir" "$upper1" "$work1" "$ovl1_mnt"
+mkdir -p "$ovl1_mnt/app/l1_new"
+echo "layer1 add" > "$ovl1_mnt/app/l1_new/new.txt"
+echo "layer1 edit" > "$ovl1_mnt/app/keep_dir/change.txt"
+rm -f "$ovl1_mnt/app/keep_dir/remove_l1.txt"
+_overlay_unmount "$ovl1_mnt"
+_build_layer_image "$upper1" "$img1"
+
+# layer 2 stacked on (upper1, basedir): whiteout file+dir, add file,
+# opaque-replace a directory so the lower subtree is fully hidden
+_mount_overlay_rw "$upper1:$basedir" "$upper2" "$work2" "$ovl2_mnt"
+rm -rf "$ovl2_mnt/app/delete_dir" "$ovl2_mnt/app/opaque_dir"
+rm -f "$ovl2_mnt/app/keep_dir/remove_l2.txt"
+mkdir -p "$ovl2_mnt/app/l2_new" "$ovl2_mnt/app/opaque_dir"
+echo "layer2 add" > "$ovl2_mnt/app/l2_new/new2.txt"
+echo "opaque replaces lower" > "$ovl2_mnt/app/opaque_dir/replacement.txt"
+_overlay_unmount "$ovl2_mnt"
+_build_layer_image "$upper2" "$img2"
+
+# merge both source images into a single self-contained image
+$MKFS_EROFS_PROG --clean=data --ovlfs-strip=1 "$SCRATCH_DEV" \
+	"$baseimg" "$img1" "$img2" >>$seqres.full 2>&1 ||
+	_fail "failed to rebuild merged image with --clean=data"
+
+# remove both source images: the merged image must be self-contained
+rm -f "$img1" "$img2"
+
+# live read-only stacked overlay of the same layers
+$MOUNT_PROG -t overlay overlay \
+	-o ro,lowerdir="$upper2:$upper1:$basedir" "$ref_mnt" \
+	>>$seqres.full 2>&1 || _fail "failed to mount reference overlay"
+_scratch_mount 2>>$seqres.full
+
+FSSUM_OPTS="-MAC"
+[ $FSTYP = "erofsfuse" ] && FSSUM_OPTS="${FSSUM_OPTS}T"
+sum_overlay=`$FSSUM_PROG $FSSUM_OPTS "$ref_mnt"`
+sum_fsmerged=`$FSSUM_PROG $FSSUM_OPTS "$SCRATCH_MNT"`
+echo "overlayfs checksum:  $sum_overlay"  >>$seqres.full
+echo "fsmerged checksum:   $sum_fsmerged" >>$seqres.full
+
+_scratch_unmount
+_overlay_unmount "$ref_mnt"
+
+[ "x$sum_overlay" = "x$sum_fsmerged" ] ||
+	_fail "overlayfs and fsmerged checksums mismatch"
+
+echo Silence is golden
+status=0
+exit 0
diff --git a/tests/erofs/030.out b/tests/erofs/030.out
new file mode 100644
index 00000000..06a1c8fe
--- /dev/null
+++ b/tests/erofs/030.out
@@ -0,0 +1,2 @@
+QA output created by 030
+Silence is golden
-- 



More information about the Linux-erofs mailing list