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

Lucas Karpinski <[email protected]>
Newsgroups org.ozlabs.lists.linux-erofs
Message-ID <[email protected]>
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 <[email protected]>
---
 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
--
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.