Skip to contentSkip to conversation

fstests: add test coverage for cloned filesystem ids

PATCH Superseded Anand Jain 3 participants 14 messages Apr 27 – Apr 28 Btrfs file system

v3 of 8 Superseded Latest is v8 →

lore
Share & tools
lore

Anand Jain

author
#0 lore raw

v3 cover fstests: add test coverage for cloned filesystem ids

v3:
Dropped the -o nouuid mount option.
Added a Btrfs test case to verify clones of filesystems using the
metadata_uuid superblock feature.
Updated _loop_image_create_clone() to handle necessary pre-clone
filesystem tuning.

v2:
https://lore.kernel.org/fstests/[email protected]

Anand Jain (9):
fstests: add _loop_image_create_clone() helper
fstests: add _clone_mount_option() helper
fstests: add test for inotify isolation on cloned devices
fstests: verify fanotify isolation on cloned filesystems
fstests: verify f_fsid for cloned filesystems
fstests: verify libblkid resolution of duplicate UUIDs
fstests: verify IMA isolation on cloned filesystems
fstests: verify exportfs file handles on cloned filesystems
fstests: btrfs: test UUID consistency for clones with metadata_uuid

 common/config         |   2 +
 common/rc             |  58 +++++++++++++++++++++
 tests/btrfs/348       |  91 +++++++++++++++++++++++++++++++++
 tests/btrfs/348.out   |  19 +++++++
 tests/generic/800     |  88 ++++++++++++++++++++++++++++++++
 tests/generic/800.out |   7 +++
 tests/generic/801     | 115 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/801.out |   7 +++
 tests/generic/802     |  61 ++++++++++++++++++++++
 tests/generic/802.out |   7 +++
 tests/generic/803     |  75 +++++++++++++++++++++++++++
 tests/generic/803.out |  19 +++++++
 tests/generic/804     | 102 +++++++++++++++++++++++++++++++++++++
 tests/generic/804.out |  10 ++++
 tests/generic/805     |  72 ++++++++++++++++++++++++++
 tests/generic/805.out |   2 +
 16 files changed, 735 insertions(+)
 create mode 100644 tests/btrfs/348
 create mode 100644 tests/btrfs/348.out
 create mode 100644 tests/generic/800
 create mode 100644 tests/generic/800.out
 create mode 100644 tests/generic/801
 create mode 100644 tests/generic/801.out
 create mode 100644 tests/generic/802
 create mode 100644 tests/generic/802.out
 create mode 100644 tests/generic/803
 create mode 100644 tests/generic/803.out
 create mode 100644 tests/generic/804
 create mode 100644 tests/generic/804.out
 create mode 100644 tests/generic/805
 create mode 100644 tests/generic/805.out
Signature · 1 line
2.43.0
author
#1 lore raw

PATCH v3 1/9 fstests: add _loop_image_create_clone() helper

Introduce _loop_image_create_clone() and _loop_image_destroy() to mkfs an image file and clone it to another image file, and attach a loop device to them. And its destroy part.

---
 common/rc | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)
common/rc +44 -0
index 9632b211b58f..0e7b7eb1d98f 100644
@@ -1503,6 +1503,50 @@ _scratch_resvblks()
 	esac
 }
 
+_loop_image_create_clone()
+{
+	local -n _ret=$1
+	local pre_clone_tune_func=$2
+	local img_file=$TEST_DIR/${seq}.img
+	local img_file_clone=$TEST_DIR/${seq}_clone.img
+	local size=$(_small_fs_size_mb 128) # Smallest possible
+	local loop_devs
+
+	size=$((size * 1024 * 1024))
+	$XFS_IO_PROG -f -c "truncate $size" $img_file
+
+	loop_devs=$(_create_loop_device $img_file)
+	_ret=($loop_devs)
+
+	case $FSTYP in
+	xfs)
+		_mkfs_dev "-s size=4096" $img_file
+		;;
+	btrfs)
+		_mkfs_dev $img_file
+		;;
+	*)
+		_mkfs_dev $img_file
+		;;
+	esac
+
+	($pre_clone_tune_func)
+
+	cp $img_file $img_file_clone
+
+	loop_devs="$loop_devs $(_create_loop_device $img_file_clone)"
+
+	_ret=($loop_devs)
+}
+
+_loop_image_destroy()
+{
+	for d in "$@"; do
+		local f=$(losetup --noheadings --output BACK-FILE $d)
+		_destroy_loop_device "$d"
+		[ -n "$f" ] && rm -f "$f"
+	done
+}
 
 # Repair scratch filesystem.  Returns 0 if the FS is good to go (either no
 # errors found or errors were fixed) and nonzero otherwise; also spits out
Signature · 1 line
2.43.0
author
#4 lore raw

PATCH v3 2/9 fstests: add _clone_mount_option() helper

In reply to Anand Jain

Adds _clone_mount_option() helper function to handle filesystem-specific requirements for mounting cloned devices. Abstract the need for -o nouuid on XFS.

---
 common/rc | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
common/rc +14 -0
index 0e7b7eb1d98f..2d28e174f605 100644
@@ -397,6 +397,20 @@ _scratch_mount_options()
 					$SCRATCH_DEV $SCRATCH_MNT
 }
 
+_clone_mount_option()
+{
+	local mount_opts=""
+
+	case "$FSTYP" in
+	xfs)
+		mount_opts="-o nouuid"
+		;;
+	*)
+	esac
+
+	echo $mount_opts
+}
+
 _supports_filetype()
 {
 	local dir=$1
Signature · 1 line
2.43.0
author
#5 lore raw

PATCH v3 3/9 fstests: add test for inotify isolation on cloned devices

In reply to Anand Jain

Add a new test, to verify that the kernel correctly differentiates between two block devices sharing the same FSID/UUID.

---
 common/config         |  1 +
 tests/generic/800     | 88 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/800.out |  7 ++++
 3 files changed, 96 insertions(+)
 create mode 100644 tests/generic/800
 create mode 100644 tests/generic/800.out
common/config +1 -0
index 4fd4c2c8af11..605a57947a40 100644
@@ -242,6 +242,7 @@ export BTRFS_MAP_LOGICAL_PROG=$(type -P btrfs-map-logical)
 export PARTED_PROG="$(type -P parted)"
 export XFS_PROPERTY_PROG="$(type -P xfs_property)"
 export FSCRYPTCTL_PROG="$(type -P fscryptctl)"
+export INOTIFYWAIT_PROG="$(type -P inotifywait)"
 
 # udev wait functions.
 #
tests/generic/800 new file +88 -0

88 lines not on this page · raw message

tests/generic/800.out new file +7 -0
new file mode 100644
index 000000000000..b10842a31210
@@ -0,0 +1,7 @@
+QA output created by 800
+Setup inotify watchers on both mnt1 and mnt2
+Trigger file creation on mnt1
+Trigger file creation on mnt2
+Verify inotify isolation
+SUCCESS: mnt1 events isolated.
+SUCCESS: mnt2 events isolated.
Signature · 1 line
2.43.0
author
#7 lore raw

PATCH v3 4/9 fstests: verify fanotify isolation on cloned filesystems

In reply to Anand Jain

Verify that fanotify events are correctly routed to the appropriate watcher when cloned filesystems are mounted.
Helps verify kernel's event notification distinguishes between devices sharing the same FSID/UUID.

---
 common/config         |   1 +
 tests/generic/801     | 115 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/801.out |   7 +++
 3 files changed, 123 insertions(+)
 create mode 100644 tests/generic/801
 create mode 100644 tests/generic/801.out
common/config +1 -0
index 605a57947a40..1588bdcb1aa1 100644
@@ -243,6 +243,7 @@ export PARTED_PROG="$(type -P parted)"
 export XFS_PROPERTY_PROG="$(type -P xfs_property)"
 export FSCRYPTCTL_PROG="$(type -P fscryptctl)"
 export INOTIFYWAIT_PROG="$(type -P inotifywait)"
+export FSNOTIFYWAIT_PROG="$(type -P fsnotifywait)"
 
 # udev wait functions.
 #
tests/generic/801 new file +115 -0

115 lines not on this page · raw message

tests/generic/801.out new file +7 -0
new file mode 100644
index 000000000000..d7b318d9f27c
@@ -0,0 +1,7 @@
+QA output created by 801
+Setup FID fanotify watchers on both mnt1 and mnt2
+Trigger file creation on mnt1
+Trigger file creation on mnt2
+Verify fsid in the fanotify
+SUCCESS: mnt1 events found
+SUCCESS: mnt2 events found
Signature · 1 line
2.43.0
author
#9 lore raw

PATCH v3 5/9 fstests: verify f_fsid for cloned filesystems

In reply to Anand Jain

Verify that the cloned filesystem provides an f_fsid that is persistent across mount cycles, yet unique from the original filesystem's f_fsid.

---
 tests/generic/802     | 61 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/802.out |  7 +++++
 2 files changed, 68 insertions(+)
 create mode 100644 tests/generic/802
 create mode 100644 tests/generic/802.out
tests/generic/802 new file +61 -0
new file mode 100644
index 000000000000..83e75dcd0d78
@@ -0,0 +1,61 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#
+# FS QA Test 802
+# Verify f_fsid and s_uuid of cloned filesystems across mount cycle.
+
+. ./common/preamble
+
+_begin_fstest auto quick mount clone
+
+_require_test
+_require_loop
+
+[ "$FSTYP" = "btrfs" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \
+	"btrfs: use on-disk uuid for s_uuid in temp_fsid mounts"
+[ "$FSTYP" = "btrfs" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \
+	"btrfs: derive f_fsid from on-disk fsuuid and dev_t"
+
+_cleanup()
+{
+	cd /
+	rm -r -f $tmp.*
+	umount $mnt1 $mnt2 2>/dev/null
+	_loop_image_destroy "${devs[@]}" 2> /dev/null
+}
+
+devs=()
+_loop_image_create_clone devs
+mkdir -p $TEST_DIR/$seq
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+
+fsid_scratch=$(stat -f -c "%i" $mnt1)
+fsid_clone=$(stat -f -c "%i" $mnt2)
+
+echo "**** fsid initially ****"
+echo $fsid_scratch | sed -e "s/$fsid_scratch/FSID_SCRATCH/g"
+echo $fsid_clone | sed -e "s/$fsid_clone/FSID_CLONE/g"
+
+# Make sure fsid still match across a mount cycle, also reverse the order.
+echo "**** fsid after mount cycle ****"
+_unmount $mnt1
+_unmount $mnt2
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+
+stat -f -c "%i" $mnt1 | sed -e "s/$fsid_scratch/FSID_SCRATCH/g"
+stat -f -c "%i" $mnt2 | sed -e "s/$fsid_clone/FSID_CLONE/g"
+
+status=0
+exit
tests/generic/802.out new file +7 -0
new file mode 100644
index 000000000000..d1e008f122bb
@@ -0,0 +1,7 @@
+QA output created by 802
+**** fsid initially ****
+FSID_SCRATCH
+FSID_CLONE
+**** fsid after mount cycle ****
+FSID_SCRATCH
+FSID_CLONE
Signature · 1 line
2.43.0
author
#10 lore raw

PATCH v3 6/9 fstests: verify libblkid resolution of duplicate UUIDs

In reply to Anand Jain

Verify how findmnt, df (libblkid) resolve device paths when multiple block devices share the same FSUUID.

---
 tests/generic/803     | 75 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/803.out | 19 +++++++++++
 2 files changed, 94 insertions(+)
 create mode 100644 tests/generic/803
 create mode 100644 tests/generic/803.out
tests/generic/803 new file +75 -0
new file mode 100644
index 000000000000..1e0fbc768050
@@ -0,0 +1,75 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#
+# FS QA Test 803
+# Verify how libblkid resolve devices when multiple devices sharing the
+# same FSUUID.
+
+. ./common/preamble
+. ./common/filter
+
+_begin_fstest auto quick mount clone
+
+_require_test
+_require_loop
+
+_cleanup()
+{
+	cd /
+	rm -r -f $tmp.*
+	umount $mnt1 $mnt2 2>/dev/null
+	_loop_image_destroy "${devs[@]}" 2> /dev/null
+}
+
+filter_pool()
+{
+	sed -e "s|${devs[0]}|DEV1|g" -e "s|${mnt1}|MNT1|g" \
+	    -e "s|${devs[1]}|DEV2|g" -e "s|${mnt2}|MNT2|g" | _filter_spaces
+}
+
+print_info()
+{
+	local mntpt=$1
+	local tgt=$(findmnt -no SOURCE $mntpt)
+	local fsuuid=$(blkid -s UUID -o value $tgt)
+
+	echo "mntpt=$mntpt tgt=$tgt fsuuid=$fsuuid" >> $seqres.full
+	echo
+	findmnt -o SOURCE,TARGET,UUID "$tgt" | tail -n +2 | \
+				sed -e "s/${fsuuid}/FSUUID/g" | filter_pool
+	awk -v dev="$tgt" '$1 == dev { print $1, $2 }' /proc/self/mounts | \
+								filter_pool
+	df --all --output=source,target "$tgt" | tail -n +2 | filter_pool
+}
+
+devs=()
+_loop_image_create_clone devs
+mkdir -p $TEST_DIR/$seq
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+
+print_info $mnt1
+print_info $mnt2
+
+echo
+echo "**** mount cycle ****"
+_unmount $mnt1
+_unmount $mnt2
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+
+print_info $mnt1
+print_info $mnt2
+
+status=0
+exit
tests/generic/803.out new file +19 -0
new file mode 100644
index 000000000000..20a1cb36a213
@@ -0,0 +1,19 @@
+QA output created by 803
+
+DEV1 MNT1 FSUUID
+DEV1 MNT1
+DEV1 MNT1
+
+DEV2 MNT2 FSUUID
+DEV2 MNT2
+DEV2 MNT2
+
+**** mount cycle ****
+
+DEV1 MNT1 FSUUID
+DEV1 MNT1
+DEV1 MNT1
+
+DEV2 MNT2 FSUUID
+DEV2 MNT2
+DEV2 MNT2
Signature · 1 line
2.43.0
author
#11 lore raw

PATCH v3 7/9 fstests: verify IMA isolation on cloned filesystems

In reply to Anand Jain

Add testcase to verify IMA measurement isolation when multiple devices share the same FSUUID.

---
 tests/generic/804     | 102 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/804.out |  10 +++++
 2 files changed, 112 insertions(+)
 create mode 100644 tests/generic/804
 create mode 100644 tests/generic/804.out
tests/generic/804 new file +102 -0
new file mode 100644
index 000000000000..5f5c04f97579
@@ -0,0 +1,102 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#
+# FS QA Test 804
+# Verify IMA isolation on cloned filesystems:
+# . Mount two devices sharing the same FSUUID (cloned).
+# . Apply an IMA policy to measure files based on that FSUUID.
+# . Create unique files on each mount point to trigger measurements.
+# . Confirm the IMA log correctly attributes events to the respective mounts.
+
+. ./common/preamble
+. ./common/filter
+
+_begin_fstest auto quick clone
+
+_require_test
+_require_loop
+
+[ "$FSTYP" = "btrfs" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \
+	"btrfs: use on-disk uuid for s_uuid in temp_fsid mounts"
+[ "$FSTYP" = "btrfs" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \
+	"btrfs: derive f_fsid from on-disk fsuuid and dev_t"
+
+_cleanup()
+{
+	cd /
+	rm -r -f $tmp.*
+	_unmount $mnt1 2>/dev/null
+	_unmount $mnt2 2>/dev/null
+	_loop_image_destroy "${devs[@]}" 2> /dev/null
+}
+
+filter_pool()
+{
+	sed -e "s|${devs[0]}|DEV1|g" -e "s|$mnt1|MNT1|g" \
+	    -e "s|${devs[1]}|DEV2|g" -e "s|$mnt2|MNT2|g" | _filter_spaces
+}
+
+do_ima()
+{
+	local ima_policy="/sys/kernel/security/ima/policy"
+	local ima_log="/sys/kernel/security/ima/ascii_runtime_measurements"
+	local fsuuid
+	local mnt=$1
+	local enable=$2
+
+	# Since the in-memory IMA audit log is only cleared upon reboot,
+	# use unique random filenames to avoid log collisions.
+	local foofile=$(mktemp --dry-run foobar_XXXXX)
+
+	echo $mnt $enable | filter_pool
+
+	[ -w "$ima_policy" ] || _notrun "IMA policy not writable"
+
+	fsuuid=$(blkid -s UUID -o value ${devs[0]})
+
+	# Load IMA policy to measure file access specifically for this
+	# filesystem UUID.
+	if [[ $enable -eq 1 ]]; then
+		echo "measure func=FILE_CHECK fsuuid=$fsuuid" > "$ima_policy" || \
+			_notrun "Policy rejected"
+	fi
+
+	# Create a file to trigger measurement and verify its entry in
+	# the IMA log.
+	echo "test_data" > $mnt/$foofile
+
+	# For $ima_log column entry please ref to
+	grep $foofile "$ima_log" | awk '{ print $5 }' | filter_pool | \
+						sed "s/$foofile/FOOBAR_FILE/"
+
+	echo "dbg: $mnt $fsuuid $foofile" >> $seqres.full
+	cat $ima_log | tail -1 >> $seqres.full
+	echo >> $seqres.full
+}
+
+devs=()
+_loop_image_create_clone devs
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+
+do_ima $mnt1 1
+do_ima $mnt2 0
+
+# Btrfs uses in-memory dynamic temp_fsid
+echo mount cycle
+_unmount $mnt2
+_mount $mount_opts ${devs[1]} $mnt2 || _fail "Failed to mount dev2"
+
+do_ima $mnt1 0
+do_ima $mnt2 0
+
+status=0
+exit
tests/generic/804.out new file +10 -0
new file mode 100644
index 000000000000..9804181d6c17
@@ -0,0 +1,10 @@
+QA output created by 804
+MNT1 1
+MNT1/FOOBAR_FILE
+MNT2 0
+MNT2/FOOBAR_FILE
+mount cycle
+MNT1 0
+MNT1/FOOBAR_FILE
+MNT2 0
+MNT2/FOOBAR_FILE
Signature · 1 line
2.43.0
author
#12 lore raw

PATCH v3 8/9 fstests: verify exportfs file handles on cloned filesystems

In reply to Anand Jain

Ensure that exportfs can correctly decode file handles on a cloned filesystem across a mount cycle, by file handles generated on a cloned device remain valid after mount cycle.

---
 tests/generic/805     | 72 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/805.out |  2 ++
 2 files changed, 74 insertions(+)
 create mode 100644 tests/generic/805
 create mode 100644 tests/generic/805.out
tests/generic/805 new file +72 -0
new file mode 100644
index 000000000000..18c92205304c
@@ -0,0 +1,72 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#
+# FS QA Test No. 805
+
+. ./common/preamble
+
+_begin_fstest auto quick exportfs clone
+
+_require_test
+_require_exportfs
+_require_loop
+_require_test_program "open_by_handle"
+
+_cleanup()
+{
+	cd /
+	rm -r -f $tmp.*
+	_unmount $mnt1 2>/dev/null
+	_unmount $mnt2 2>/dev/null
+	_loop_image_destroy "${devs[@]}" 2> /dev/null
+}
+
+# Create test dir and test files, encode file handles and store to tmp file
+create_test_files()
+{
+	rm -rf $testdir
+	mkdir -p $testdir
+	$here/src/open_by_handle -cwp -o $tmp.handles_file $testdir $NUMFILES
+}
+
+# Decode file handles loaded from tmp file
+test_file_handles()
+{
+	local opt=$1
+	local when=$2
+
+	echo test_file_handles after $when
+	$here/src/open_by_handle $opt -i $tmp.handles_file $mnt2 $NUMFILES
+}
+
+devs=()
+_loop_image_create_clone devs
+mkdir -p $TEST_DIR/$seq
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+
+NUMFILES=1
+testdir=$mnt2/testdir
+
+# Decode file handles of files/dir after cycle mount
+create_test_files
+
+_unmount $mnt1
+_unmount $mnt2
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+
+test_file_handles -rp "cycle mount"
+
+status=0
+exit
tests/generic/805.out new file +2 -0
new file mode 100644
index 000000000000..29b11ec77ffb
@@ -0,0 +1,2 @@
+QA output created by 805
+test_file_handles after cycle mount
Signature · 1 line
2.43.0
author
#13 lore raw

PATCH v3 9/9 fstests: btrfs: test UUID consistency for clones with metadata_uuid

In reply to Anand Jain

Btrfs uses the metadata_uuid superblock feature to change the on-disk UUID without rewriting every block header. This patch adds a sanity check to ensure UUID consistency when a filesystem with metadata_uuid enabled is cloned.

---
 tests/btrfs/348     | 91 +++++++++++++++++++++++++++++++++++++++++++++
 tests/btrfs/348.out | 19 ++++++++++
 2 files changed, 110 insertions(+)
 create mode 100644 tests/btrfs/348
 create mode 100644 tests/btrfs/348.out
tests/btrfs/348 new file +91 -0
new file mode 100644
index 000000000000..cfa56fdb4a4a
@@ -0,0 +1,91 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#
+# FS QA Test 348
+#
+# Verify that the cloned filesystem UUID remains consistent, even when the
+# `metadata_uuid` feature is enabled.
+#
+
+. ./common/preamble
+. ./common/filter
+
+_begin_fstest auto quick mount clone
+
+_require_test
+_require_loop
+
+_cleanup()
+{
+	cd /
+	rm -r -f $tmp.*
+	umount $mnt1 $mnt2 2>/dev/null
+	_loop_image_destroy "${devs[@]}" 2> /dev/null
+}
+
+filter_pool()
+{
+	sed -e "s|${devs[0]}|DEV1|g" -e "s|${mnt1}|MNT1|g" \
+	    -e "s|${devs[1]}|DEV2|g" -e "s|${mnt2}|MNT2|g" | _filter_spaces
+}
+
+pre_clone_tune()
+{
+	local temp_mnt=$TEST_DIR/${seq}_mnt
+
+	_require_command "$BTRFS_TUNE_PROG" btrfstune
+	mkdir -p $temp_mnt
+	_mount ${devs[0]} $temp_mnt
+	$BTRFS_UTIL_PROG subvolume create $temp_mnt/sv1 &> /dev/null
+	_unmount $temp_mnt
+	rm -r -f $temp_mnt
+
+	$BTRFS_TUNE_PROG -m ${devs[0]}
+}
+
+print_info()
+{
+	local mntpt=$1
+	local tgt=$(findmnt -no SOURCE $mntpt)
+	local fsuuid=$(blkid -s UUID -o value $tgt)
+
+	echo "mntpt=$mntpt tgt=$tgt fsuuid=$fsuuid" >> $seqres.full
+	echo
+	findmnt -o SOURCE,TARGET,UUID "$tgt" | tail -n +2 | \
+				sed -e "s/${fsuuid}/FSUUID/g" | filter_pool
+	awk -v dev="$tgt" '$1 == dev { print $1, $2 }' /proc/self/mounts | \
+								filter_pool
+	df --all --output=source,target "$tgt" | tail -n +2 | filter_pool
+}
+
+devs=()
+_loop_image_create_clone devs pre_clone_tune
+mkdir -p $TEST_DIR/$seq
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+
+print_info $mnt1
+print_info $mnt2
+
+echo
+echo "**** mount cycle ****"
+_unmount $mnt1
+_unmount $mnt2
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+						_fail "Failed to mount dev2"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+						_fail "Failed to mount dev1"
+
+print_info $mnt1
+print_info $mnt2
+
+status=0
+exit
tests/btrfs/348.out new file +19 -0
new file mode 100644
index 000000000000..4b102e986246
@@ -0,0 +1,19 @@
+QA output created by 348
+
+DEV1 MNT1 FSUUID
+DEV1 MNT1
+DEV1 MNT1
+
+DEV2 MNT2 FSUUID
+DEV2 MNT2
+DEV2 MNT2
+
+**** mount cycle ****
+
+DEV1 MNT1 FSUUID
+DEV1 MNT1
+DEV1 MNT1
+
+DEV2 MNT2 FSUUID
+DEV2 MNT2
+DEV2 MNT2
Signature · 1 line
2.43.0
Reviewed-by on v3 3/9
#6 lore raw

Re: [PATCH v3 3/9] fstests: add test for inotify isolation on cloned devices

In reply to Anand Jain
Quoted 5 lines · Anand Jain

Add a new test, to verify that the kernel correctly differentiates between two block devices sharing the same FSID/UUID.

Quoted 130 lines
---
 common/config         |  1 +
 tests/generic/800     | 88 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/800.out |  7 ++++
 3 files changed, 96 insertions(+)
 create mode 100644 tests/generic/800
 create mode 100644 tests/generic/800.out
common/config +1 -0
index 4fd4c2c8af11..605a57947a40 100644
@@ -242,6 +242,7 @@ export BTRFS_MAP_LOGICAL_PROG=$(type -P btrfs-map-logical)
 export PARTED_PROG="$(type -P parted)"
 export XFS_PROPERTY_PROG="$(type -P xfs_property)"
 export FSCRYPTCTL_PROG="$(type -P fscryptctl)"
+export INOTIFYWAIT_PROG="$(type -P inotifywait)"
 
 # udev wait functions.
 #
tests/generic/800 new file +88 -0
new file mode 100644
index 000000000000..16bc1159a2e1
@@ -0,0 +1,88 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#
+# FS QA Test 800
+#
+# Verify if the kernel or userspace becomes confused when two block devices
+# share the same fid/fsid/uuid. Create inotify on both original and cloned
+# filesystem. Monitor the notification in the respective logs.
+
+. ./common/preamble
+
+_begin_fstest auto quick mount clone
+
+_require_test
+_require_loop
+_require_command "$INOTIFYWAIT_PROG" inotifywait
+
+_cleanup()
+{
+       cd /
+       [[ -n $pid1 ]] && { kill -TERM "$pid1" 2> /dev/null; wait $pid1; }
+       [[ -n $pid2 ]] && { kill -TERM "$pid2" 2> /dev/null; wait $pid2; }
+       rm -r -f $tmp.*
+       _unmount $mnt1 2>/dev/null
+       _unmount $mnt2 2>/dev/null
+       _loop_image_destroy "${devs[@]}" 2> /dev/null
+}
+
+devs=()
+_loop_image_create_clone devs
+mkdir -p $TEST_DIR/$seq
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+                                               _fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+                                               _fail "Failed to mount dev2"
+
+log1=$tmp.inotify1
+log2=$tmp.inotify2
+
+pid1=""
+pid2=""
+echo "Setup inotify watchers on both mnt1 and mnt2"
+$INOTIFYWAIT_PROG -m -e create --format '%f' $mnt1 > $log1 2>&1 &
+pid1=$!
+$INOTIFYWAIT_PROG -m -e create --format '%f' $mnt2 > $log2 2>&1 &
+pid2=$!
+sleep 2
+
+echo "Trigger file creation on mnt1"
+touch $mnt1/file_on_mnt1
+sync
+sleep 1
+
+echo "Trigger file creation on mnt2"
+touch $mnt2/file_on_mnt2
+sync
+sleep 1
+
+echo "Verify inotify isolation"
+kill $pid1 $pid2
+wait $pid1 $pid2 2>/dev/null
+pid1=""
+pid2=""
+
+if grep -q "file_on_mnt1" $log1 && ! grep -q "file_on_mnt2" $log1; then
+       echo "SUCCESS: mnt1 events isolated."
+else
+       echo "FAIL: mnt1 inotify confusion!"
+       [ ! -s $log1 ] && echo "  - mnt1 received no events."
+       grep -q "file_on_mnt2" $log1 && echo "  - mnt1 received event from mnt2."
+fi
+
+if grep -q "file_on_mnt2" $log2 && ! grep -q "file_on_mnt1" $log2; then
+       echo "SUCCESS: mnt2 events isolated."
+else
+       echo "FAIL: mnt2 inotify confusion!"
+       [ ! -s $log2 ] && echo "  - mnt2 received no events."
+       grep -q "file_on_mnt1" $log2 && echo "  - mnt2 received event from mnt1."
+fi
+
+status=0
+exit
tests/generic/800.out new file +7 -1
new file mode 100644
index 000000000000..b10842a31210
@@ -0,0 +1,7 @@
+QA output created by 800
+Setup inotify watchers on both mnt1 and mnt2
+Trigger file creation on mnt1
+Trigger file creation on mnt2
+Verify inotify isolation
+SUCCESS: mnt1 events isolated.
+SUCCESS: mnt2 events isolated.
--

2.43.0

#2 lore raw

Re: [PATCH v3 1/9] fstests: add _loop_image_create_clone() helper

In reply to Anand Jain
Quoted 32 lines · Anand Jain

Introduce _loop_image_create_clone() and _loop_image_destroy() to mkfs an image file and clone it to another image file, and attach a loop device to them. And its destroy part.

---
 common/rc | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)
common/rc +14 -0
index 9632b211b58f..0e7b7eb1d98f 100644
@@ -1503,6 +1503,50 @@ _scratch_resvblks()
        esac
 }
 
+_loop_image_create_clone()
+{
+       local -n _ret=$1
+       local pre_clone_tune_func=$2
+       local img_file=$TEST_DIR/${seq}.img
+       local img_file_clone=$TEST_DIR/${seq}_clone.img
+       local size=$(_small_fs_size_mb 128) # Smallest possible
+       local loop_devs
+
+       size=$((size * 1024 * 1024))
+       $XFS_IO_PROG -f -c "truncate $size" $img_file
+
+       loop_devs=$(_create_loop_device $img_file)
+       _ret=($loop_devs)

common/rc _scratch_resvblks() · line 1529

Quoted 10 lines
common/rc +10 -0
+
+       case $FSTYP in
+       xfs)
+               _mkfs_dev "-s size=4096" $img_file
+               ;;
+       btrfs)
+               _mkfs_dev $img_file
+               ;;
+       *)
+               _mkfs_dev $img_file

You making a wrong assumption that FSTYP can format the loop devices

You should add _require_block_device $SCRATCH_DEV
to all your tests or maybe nicer, add:

_require_loop_mountable which requires loop and block dev fs and use this requirement instead of just _require_loop in all tests.

Thanks,
Amir.

#8 lore raw

Re: [PATCH v3 4/9] fstests: verify fanotify isolation on cloned filesystems

In reply to Anand Jain
Quoted 37 lines · Anand Jain

Verify that fanotify events are correctly routed to the appropriate watcher when cloned filesystems are mounted.
Helps verify kernel's event notification distinguishes between devices sharing the same FSID/UUID.

---
 common/config         |   1 +
 tests/generic/801     | 115 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/801.out |   7 +++
 3 files changed, 123 insertions(+)
 create mode 100644 tests/generic/801
 create mode 100644 tests/generic/801.out
common/config +1 -0
index 605a57947a40..1588bdcb1aa1 100644
@@ -243,6 +243,7 @@ export PARTED_PROG="$(type -P parted)"
 export XFS_PROPERTY_PROG="$(type -P xfs_property)"
 export FSCRYPTCTL_PROG="$(type -P fscryptctl)"
 export INOTIFYWAIT_PROG="$(type -P inotifywait)"
+export FSNOTIFYWAIT_PROG="$(type -P fsnotifywait)"
 
 # udev wait functions.
 #
tests/generic/801 new file +4 -0
new file mode 100644
index 000000000000..3f64e4de2206
@@ -0,0 +1,115 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Anand Jain <[email protected]>.  All Rights Reserved.
+#

tests/generic/801 line 14

Quoted 10 lines
tests/generic/801 new file +10 -0
+# FS QA Test 801
+# Verify fanotify FID functionality on cloned filesystems by setting up
+# watchers and making sure notifications are in the correct logs files.
+
+. ./common/preamble
+
+_begin_fstest auto quick mount clone
+
+_require_test
+_require_loop

insufficient check for mounting a blockdev fs

tests/generic/801 line 17

Quoted 3 lines
tests/generic/801 new file +3 -0
+_require_command "$FSNOTIFYWAIT_PROG" fsnotifywait
+
+[ "$FSTYP" = "ext4" ] && _fixed_by_kernel_commit xxxxxxxxxxxx \

Fix before merging

Other than that it looks ok.

Thanks,
Amir.

Quoted 114 lines
tests/generic/801 new file +98 -0
+       "ext4: derive f_fsid from block device to avoid collisions"
+
+_cleanup()
+{
+       cd /
+       [[ -n $pid1 ]] && { kill -TERM "$pid1" 2> /dev/null; wait $pid1; }
+       [[ -n $pid2 ]] && { kill -TERM "$pid2" 2> /dev/null; wait $pid2; }
+       umount $mnt1 $mnt2 2>/dev/null
+       _loop_image_destroy "${devs[@]}" 2> /dev/null
+       rm -r -f $tmp.*
+}
+
+monitor_fanotify()
+{
+       local mmnt=$1
+       exec stdbuf -oL $FSNOTIFYWAIT_PROG -m -F -S -e create "$mmnt" 2>&1
+}
+
+fsid_to_fid_parts()
+{
+       local fsid=$1
+       # Pad to 16 hex chars (64-bit), then split into two 32-bit halves
+       local padded=$(printf '%016x' "0x${fsid}")
+       local hi=$(printf '%x' "0x${padded:0:8}")   # strips leading zeros
+       local lo=$(printf '%x' "0x${padded:8:8}")   # strips leading zeros
+       echo "${hi}.${lo}"
+}
+
+devs=()
+_loop_image_create_clone devs
+mkdir -p $TEST_DIR/$seq
+mnt1=$TEST_DIR/$seq/mnt1
+mnt2=$TEST_DIR/$seq/mnt2
+mkdir -p $mnt1
+mkdir -p $mnt2
+
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[0]} $mnt1 || \
+                                               _fail "Failed to mount dev1"
+_mount $(_common_dev_mount_options) $(_clone_mount_option) ${devs[1]} $mnt2 || \
+                                               _fail "Failed to mount dev2"
+
+fsid1=$(stat -f -c "%i" $mnt1)
+fsid2=$(stat -f -c "%i" $mnt2)
+
+[[ "$fsid1" == "$fsid2" ]] && \
+       _notrun "Require clone filesystem with unique f_fsid"
+
+log1=$tmp.fanotify1
+log2=$tmp.fanotify2
+
+pid1=""
+pid2=""
+echo "Setup FID fanotify watchers on both mnt1 and mnt2"
+( monitor_fanotify "$mnt1" > "$log1" ) &
+pid1=$!
+( monitor_fanotify "$mnt2" > "$log2" ) &
+pid2=$!
+sleep 2
+
+echo "Trigger file creation on mnt1"
+touch $mnt1/file_on_mnt1
+sync
+sleep 1
+
+echo "Trigger file creation on mnt2"
+touch $mnt2/file_on_mnt2
+sync
+sleep 1
+
+echo "Verify fsid in the fanotify"
+kill $pid1 $pid2
+wait $pid1 $pid2 2>/dev/null
+pid1=""
+pid2=""
+
+e_fsid1=$(fsid_to_fid_parts "$fsid1")
+e_fsid2=$(fsid_to_fid_parts "$fsid2")
+
+echo $fsid1 $e_fsid1 $fsid2 $e_fsid2 >> $seqres.full
+cat $log1 >> $seqres.full
+cat $log2 >> $seqres.full
+
+if grep -qF "$e_fsid1" "$log1" && ! grep -qF "$e_fsid2" "$log1"; then
+       echo "SUCCESS: mnt1 events found"
+else
+       [ ! -s "$log1" ] && echo "  - mnt1 received no events."
+       grep -qF "$e_fsid2" "$log1" && echo "  - mnt1 received event from mnt2."
+fi
+
+if grep -qF "$e_fsid2" "$log2" && ! grep -qF "$e_fsid1" "$log2"; then
+       echo "SUCCESS: mnt2 events found"
+else
+       [ ! -s "$log2" ] && echo "  - mnt2 received no events."
+       grep -qF "$e_fsid1" "$log2" && echo "  - mnt2 received event from mnt1."
+fi
+
+status=0
+exit
tests/generic/801.out new file +7 -1
new file mode 100644
index 000000000000..d7b318d9f27c
@@ -0,0 +1,7 @@
+QA output created by 801
+Setup FID fanotify watchers on both mnt1 and mnt2
+Trigger file creation on mnt1
+Trigger file creation on mnt2
+Verify fsid in the fanotify
+SUCCESS: mnt1 events found
+SUCCESS: mnt2 events found
--

2.43.0

#3 lore raw

Re: [PATCH v3 1/9] fstests: add _loop_image_create_clone() helper

In reply to Amir Goldstein
Quoted 46 lines · Amir Goldstein
Quoted 32 lines · Anand Jain

Introduce _loop_image_create_clone() and _loop_image_destroy() to mkfs an image file and clone it to another image file, and attach a loop device to them. And its destroy part.

---
 common/rc | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)
common/rc +14 -0
index 9632b211b58f..0e7b7eb1d98f 100644
@@ -1503,6 +1503,50 @@ _scratch_resvblks()
        esac
 }
 
+_loop_image_create_clone()
+{
+       local -n _ret=$1
+       local pre_clone_tune_func=$2
+       local img_file=$TEST_DIR/${seq}.img
+       local img_file_clone=$TEST_DIR/${seq}_clone.img
+       local size=$(_small_fs_size_mb 128) # Smallest possible
+       local loop_devs
+
+       size=$((size * 1024 * 1024))
+       $XFS_IO_PROG -f -c "truncate $size" $img_file
+
+       loop_devs=$(_create_loop_device $img_file)
+       _ret=($loop_devs)

common/rc _scratch_resvblks() · line 1529

Quoted 10 lines
common/rc +10 -0
+
+       case $FSTYP in
+       xfs)
+               _mkfs_dev "-s size=4096" $img_file
+               ;;
+       btrfs)
+               _mkfs_dev $img_file
+               ;;
+       *)
+               _mkfs_dev $img_file

You making a wrong assumption that FSTYP can format the loop devices

Quoted 6 lines

You should add _require_block_device $SCRATCH_DEV
to all your tests or maybe nicer, add:

_require_loop_mountable which requires loop and block dev fs and use this requirement instead of just _require_loop in all tests.

Thanks. I've fixed this in v4 by adding
_require_block_device $TEST_DEV
Since SCRATCH_DEV is not required.

I didn't create a new _require_loop_mountable() helper. Both _require_loop() and _require_block_device() are used both together across other existing tests; I prefer not to deviate into cleaning that up in this patch set.

v4 is in the ML for rvb.

Thanks, Anand

Quoted 2 lines

Thanks,
Amir.