Unfortunately, erofs-utils currently lacks image injection support,
so the test has to hard-code the relevant offsets.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Yifan Zhao <[email protected]>
---
 tests/Makefile.am   |   3 +
 tests/erofs/032     | 135 ++++++++++++++++++++++++++++++++++++++++++++
 tests/erofs/032.out |   2 +
 3 files changed, 140 insertions(+)
 create mode 100755 tests/erofs/032
 create mode 100644 tests/erofs/032.out

diff --git a/tests/Makefile.am b/tests/Makefile.am
index a1e31fb..3c41eb3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -132,6 +132,9 @@ TESTS += erofs/030
 # 031 - test chunk-based inodes
 TESTS += erofs/031
 
+# 032 - regression test for buffered reads across device chunks
+TESTS += erofs/032
+
 # NEW TEST CASE HERE
 # TESTS += erofs/999
 
diff --git a/tests/erofs/032 b/tests/erofs/032
new file mode 100755
index 0000000..416ff5e
--- /dev/null
+++ b/tests/erofs/032
@@ -0,0 +1,135 @@
+#!/bin/sh
+# SPDX-License-Identifier: MIT
+#
+# Regression test for buffered reads crossing EROFS device chunks.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$(echo $0 | awk '{print $((NF-1))"/"$NF}' FS="/")
+
+# get standard environment, filters and checks
+. "${srcdir}/common/rc"
+
+cleanup()
+{
+       [ -n "$mnt" ] && $UMOUNT_PROG "$mnt" 2>/dev/null
+
+       for dev in "$meta_loop" "$dev1_loop" "$dev2_loop" "$dev3_loop"; do
+               [ -n "$dev" ] && losetup -d "$dev" 2>/dev/null
+       done
+
+       rm -rf $tmp.*
+}
+
+_require_root
+[ "$FSTYP" = "erofsfuse" ] && _notrun "kernel EROFS is required"
+_require_erofs
+which losetup >/dev/null 2>&1 || _notrun "losetup is not found"
+which blockdev >/dev/null 2>&1 || _notrun "blockdev is not found"
+[ -w /proc/sys/vm/drop_caches ] || _notrun "drop_caches is not writable"
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+echo "QA output created by $seq"
+
+chunk=4096
+localdir="$tmp/$seq"
+src="$localdir/src"
+mnt="$localdir/mnt"
+img="$localdir/merged.erofs"
+blob="$localdir/blob.bin"
+dev1="$localdir/device1.bin"
+dev2="$localdir/device2.bin"
+dev3="$localdir/device3.bin"
+expected="$localdir/expected.bin"
+affected="$localdir/affected.bin"
+actual="$localdir/actual.bin"
+
+mkdir -p "$src" "$mnt" || _fail "failed to create test directories"
+
+emit_byte()
+{
+       dd if=/dev/zero bs="$1" count=1 2>/dev/null | tr '\000' "$2"
+}
+
+write_at()
+{
+       printf '%b' "$1" | dd of="$2" bs=1 seek="$3" conv=notrunc \
+               >>$seqres.full 2>&1 || _fail "failed to patch image"
+}
+
+setup_loop()
+{
+       loopdev=`losetup -f 2>/dev/null` || _notrun "no free loop device"
+       losetup "$loopdev" "$1" >>$seqres.full 2>&1 || \
+               _fail "failed to setup loop device"
+}
+
+emit_byte $chunk X > "$src/mem.bin" || _fail "failed to create source file"
+emit_byte $chunk Y >> "$src/mem.bin" || _fail "failed to create source file"
+emit_byte $chunk Z >> "$src/mem.bin" || _fail "failed to create source file"
+
+emit_byte $((chunk * 3)) A > "$dev1" || _fail "failed to create device 1"
+emit_byte $chunk P > "$dev2" || _fail "failed to create device 2"
+emit_byte $((chunk * 2)) B >> "$dev2" || _fail "failed to create device 2"
+emit_byte $((chunk * 2)) P > "$dev3" || _fail "failed to create device 3"
+emit_byte $chunk C >> "$dev3" || _fail "failed to create device 3"
+
+emit_byte $chunk A > "$expected" || _fail "failed to create expected output"
+emit_byte $chunk B >> "$expected" || _fail "failed to create expected output"
+emit_byte $chunk C >> "$expected" || _fail "failed to create expected output"
+
+emit_byte $((chunk * 3)) A > "$affected" || _fail "failed to create affected 
output"
+
+"$MKFS_EROFS_PROG" -T0 -Eforce-inode-compact,nosbcrc --chunksize=$chunk \
+       --blobdev="$blob" "$img" "$src" >>$seqres.full 2>&1 || \
+       _fail "failed to create chunk-based image"
+
+# Convert mkfs' single blob-device image into three blob devices whose chunk
+# block addresses are contiguous.  A buggy kernel can merge the buffered read
+# bios across those iomap/device boundaries and read A/A/A instead of A/B/C.
+write_at '\003\000' "$img" $((1024 + 86))
+write_at '\030\000' "$img" $((1024 + 88))
+write_at '\000\000\001\000\000\000\000\000' "$img" 1408
+write_at '\000\000\002\000\001\000\000\000' "$img" 1416
+write_at '\000\000\003\000\002\000\000\000' "$img" 1424
+dd if=/dev/zero of="$img" bs=128 count=3 seek=24 conv=notrunc \
+       >>$seqres.full 2>&1 || _fail "failed to patch device table"
+write_at '\003\000\000\000' "$img" $((3072 + 64))
+write_at '\003\000\000\000' "$img" $((3072 + 128 + 64))
+write_at '\003\000\000\000' "$img" $((3072 + 256 + 64))
+
+setup_loop "$img"
+meta_loop="$loopdev"
+setup_loop "$dev1"
+dev1_loop="$loopdev"
+setup_loop "$dev2"
+dev2_loop="$loopdev"
+setup_loop "$dev3"
+dev3_loop="$loopdev"
+
+$MOUNT_PROG -t erofs \
+       -o device=$dev1_loop,device=$dev2_loop,device=$dev3_loop \
+       "$meta_loop" "$mnt" >>$seqres.full 2>&1 || \
+       _notrun "this test requires EROFS multiple device support"
+
+for dev in "$meta_loop" "$dev1_loop" "$dev2_loop" "$dev3_loop"; do
+       blockdev --setra 5120 "$dev" >>$seqres.full 2>&1 || \
+               _fail "failed to set readahead"
+done
+
+sync
+echo 3 > /proc/sys/vm/drop_caches || _notrun "failed to drop page cache"
+
+cat "$mnt/mem.bin" > "$actual" || _fail "failed to read mem.bin"
+
+if cmp -s "$actual" "$affected"; then
+       _fail "buffered read merged across device chunks"
+fi
+
+cmp -s "$actual" "$expected" || _fail "unexpected data from device chunks"
+
+echo Silence is golden
+status=0
+exit 0
diff --git a/tests/erofs/032.out b/tests/erofs/032.out
new file mode 100644
index 0000000..34e059f
--- /dev/null
+++ b/tests/erofs/032.out
@@ -0,0 +1,2 @@
+QA output created by 032
+Silence is golden
-- 
2.54.0


Reply via email to