All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
@ 2026-07-03 12:54 Pankaj Raghav
  2026-07-07  3:08 ` Zhang Yi
  0 siblings, 1 reply; 7+ messages in thread
From: Pankaj Raghav @ 2026-07-03 12:54 UTC (permalink / raw)
  To: zlang
  Cc: Zhang Yi, fstests, hch, djwong, yi.zhang, pankaj.raghav,
	linux-xfs, Pankaj Raghav

During the review of WRITE_ZEROES support to XFS, Zhang Yi pointed out
some important semantics for the boundary blocks when WRITE_ZEROES
command is used.[1]

The test cases check the boundary blocks when they are in different
states and WRITE_ZEROES are issued that straddle the boundary. Along with that,
test cases have been added when a EoF straddles an allocation unit.

[1] https://lore.kernel.org/linux-xfs/0b7f1a4f-da1c-4297-8099-98d738070ab7@huaweicloud.com/

Signed-off-by: Pankaj Raghav <p.raghav@samsung.com>
---
 common/rc             |   2 +-
 tests/generic/795     | 172 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/795.out |  16 ++++
 3 files changed, 189 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/795
 create mode 100644 tests/generic/795.out

diff --git a/common/rc b/common/rc
index 79189e7e..5e59968f 100644
--- a/common/rc
+++ b/common/rc
@@ -3008,7 +3008,7 @@ _require_xfs_io_command()
 		testio=`$XFS_IO_PROG -F -f -c "$command $param 0 1m" $testfile 2>&1`
 		param_checked="$param"
 		;;
-	"fpunch" | "fcollapse" | "zero" | "fzero" | "finsert" | "funshare")
+	"fpunch" | "fcollapse" | "zero" | "fzero" | "finsert" | "funshare" | "fwzero")
 		local blocksize=$(_get_file_block_size $TEST_DIR)
 		testio=`$XFS_IO_PROG -F -f -c "pwrite 0 $((5 * $blocksize))" \
 			-c "fsync" -c "$command $blocksize $((2 * $blocksize))" \
diff --git a/tests/generic/795 b/tests/generic/795
new file mode 100755
index 00000000..2f812f61
--- /dev/null
+++ b/tests/generic/795
@@ -0,0 +1,172 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Samsung Electronics.  All Rights Reserved.
+#
+# FS QA Test 795
+#
+# Verify FALLOC_FL_WRITE_ZEROES (xfs_io "fwzero") on an unaligned range,
+# exercising every state the two boundary allocation units can be in.
+#
+# WRITE_ZEROES must leave the whole requested range backed by *written*
+# (zeroed) extents while preserving the out-of-range bytes of the partial
+# boundary units. The possible scenarios are: written_edges, hole_edges,
+# unwritten_edges and delalloc_edges.
+#
+# It then covers the EOF cases: a WRITE_ZEROES that *extends* the file to a
+# non-unit-aligned size, followed by a further extension. WRITE_ZEROES must
+# never leave written blocks beyond EOF.
+
+. ./common/preamble
+_begin_fstest auto quick prealloc fiemap
+
+testfile=$TEST_DIR/$seq.testfile
+
+_cleanup()
+{
+	cd /
+	rm -r -f $tmp.*
+	rm -f $testfile
+}
+
+. ./common/filter
+. ./common/punch
+
+_require_test
+_require_xfs_io_command "fwzero"
+_require_xfs_io_command "falloc"
+_require_xfs_io_command "fiemap"
+_require_xfs_io_command "truncate"
+
+# The allocation unit: rt extent size on an rt config, otherwise the block
+# size. Deriving the range from this makes the head/tail units straddle rt
+# extents when rextsize > 1.
+u=$(_get_file_block_size $TEST_DIR)
+
+# The fs block size. On rt with rextsize > 1 the allocation unit u is the rt
+# extent (u > b). A WRITE_ZEROES that extends EOF must only write out to the
+# block-rounded EOF and leave the rest of the straddling rt extent unwritten,
+# rather than written past EOF.
+b=$(_get_block_size $TEST_DIR)
+
+# Unaligned range within EOF: start 1.5 units in, span 4 units, so both the
+# head unit [u, 2u) and the tail unit [5u, 6u) are only partially covered,
+# with fully covered units in between and data on both sides.
+off=$((u + u / 2))
+len=$((4 * u))
+filesz=$((8 * u))
+pattern=0xab
+
+# Expected images.  For the written/delalloc cases the pattern surrounds the
+# zeroed range; for the hole/unwritten cases the file reads back all zeroes.
+$XFS_IO_PROG -f -c "pwrite -S $pattern 0 $filesz" $tmp.pattern >> $seqres.full 2>&1
+$XFS_IO_PROG -c "pwrite -S 0 $off $len" $tmp.pattern >> $seqres.full 2>&1
+$XFS_IO_PROG -f -c "truncate $filesz" $tmp.zero >> $seqres.full 2>&1
+
+# Assert the whole requested range is backed by written extents immediately
+# after fwzero (before any sync/remount): no holes, no unwritten.
+_check_range_written()
+{
+	local file=$1
+	local bad
+
+	bad=$($XFS_IO_PROG -c "fiemap -v $off $len" "$file" | \
+		_filter_fiemap | grep -E -c 'hole|unwritten')
+	if [ "$bad" -eq 0 ]; then
+		echo "extents: range fully written"
+	else
+		echo "extents: FAIL - $bad hole/unwritten extent(s) in range"
+		$XFS_IO_PROG -c "fiemap -v $off $len" "$file" | _filter_fiemap
+	fi
+}
+
+_check_data()
+{
+	local file=$1
+	local want=$2
+
+	if cmp -s "$file" "$want"; then
+		echo "data: matches expected image"
+	else
+		echo "data: FAIL - mismatch against expected image"
+		cmp "$file" "$want" | head
+	fi
+}
+
+# Assert [start, start+len) has no written extents (unwritten/hole only).  Used
+# to verify the tail of the straddling rt extent, past the block-rounded EOF,
+# was left unwritten.  len <= 0 means u == block size (no rt tail) -> nothing to
+# check, but still emit the line so the output is config-independent.
+_check_eof_tail()
+{
+	local file=$1 start=$2 len=$3 bad
+
+	if [ "$len" -le 0 ]; then
+		echo "extents: eof tail unwritten"
+		return
+	fi
+	bad=$($XFS_IO_PROG -c "fiemap -v $start $len" "$file" | \
+		_filter_fiemap | grep -E -c 'data')
+	if [ "$bad" -eq 0 ]; then
+		echo "extents: eof tail unwritten"
+	else
+		echo "extents: FAIL - $bad written extent(s) past block-rounded EOF"
+		$XFS_IO_PROG -c "fiemap -v $start $len" "$file" | _filter_fiemap
+	fi
+}
+
+_run_case()
+{
+	local name=$1
+	local want=$2
+
+	echo "=== $name ==="
+
+	$XFS_IO_PROG -c "fwzero $off $len" $testfile >> $seqres.full 2>&1
+
+	echo "== $name fiemap after fwzero ==" >> $seqres.full
+	$XFS_IO_PROG -c "fiemap -v" $testfile >> $seqres.full 2>&1
+
+	_check_range_written $testfile
+	_test_cycle_mount
+	_check_data $testfile $want
+
+	rm -f $testfile
+}
+
+# 1) Boundary units already written, within i_size.
+$XFS_IO_PROG -f -c "pwrite -S $pattern 0 $filesz" -c fsync $testfile \
+	>> $seqres.full 2>&1
+_run_case "written_edges" $tmp.pattern
+
+# 2) Boundary units are holes (sparse file).
+$XFS_IO_PROG -f -c "truncate $filesz" $testfile >> $seqres.full 2>&1
+_run_case "hole_edges" $tmp.zero
+
+# 3) Boundary units are unwritten preallocation.
+$XFS_IO_PROG -f -c "falloc 0 $filesz" $testfile >> $seqres.full 2>&1
+_run_case "unwritten_edges" $tmp.zero
+
+# 4) Boundary units are dirty/delalloc:
+$XFS_IO_PROG -f -c "pwrite -S $pattern 0 $filesz" $testfile >> $seqres.full 2>&1
+_run_case "delalloc_edges" $tmp.pattern
+
+# 5) EOF cases. A WRITE_ZEROES that extends the file to a non-unit-aligned
+#    size, followed by a further extend.
+eof=$((3 * u + 1))
+
+echo "=== eof_then_truncate ==="
+$XFS_IO_PROG -f -c "fwzero 0 $eof" $testfile >> $seqres.full 2>&1
+# The write must stop at the block-rounded EOF; the rest of the rt extent that
+# straddles EOF must be unwritten, not written past EOF.  Round EOF up to the
+# fs block size (b is a power of two) the same way as generic/219.
+eof_ru=$(( (eof + b - 1) & ~(b - 1) ))
+_check_eof_tail $testfile $eof_ru $((4 * u - eof_ru))
+
+$XFS_IO_PROG -c "truncate $filesz" $testfile >> $seqres.full 2>&1
+_test_cycle_mount
+_check_data $testfile $tmp.zero
+rm -f $testfile
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/795.out b/tests/generic/795.out
new file mode 100644
index 00000000..7183bfa9
--- /dev/null
+++ b/tests/generic/795.out
@@ -0,0 +1,16 @@
+QA output created by 795
+=== written_edges ===
+extents: range fully written
+data: matches expected image
+=== hole_edges ===
+extents: range fully written
+data: matches expected image
+=== unwritten_edges ===
+extents: range fully written
+data: matches expected image
+=== delalloc_edges ===
+extents: range fully written
+data: matches expected image
+=== eof_then_truncate ===
+extents: eof tail unwritten
+data: matches expected image

base-commit: ffc8bad17e5b2f56e48dbac43f7c5ae8ac368fe5
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
  2026-07-03 12:54 [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES Pankaj Raghav
@ 2026-07-07  3:08 ` Zhang Yi
  2026-07-07  5:10   ` Christoph Hellwig
  0 siblings, 1 reply; 7+ messages in thread
From: Zhang Yi @ 2026-07-07  3:08 UTC (permalink / raw)
  To: Pankaj Raghav, zlang
  Cc: fstests, hch, djwong, yi.zhang, pankaj.raghav, linux-xfs

On 7/3/2026 8:54 PM, Pankaj Raghav wrote:
> During the review of WRITE_ZEROES support to XFS, Zhang Yi pointed out
> some important semantics for the boundary blocks when WRITE_ZEROES
> command is used.[1]
> 
> The test cases check the boundary blocks when they are in different
> states and WRITE_ZEROES are issued that straddle the boundary. Along with that,
> test cases have been added when a EoF straddles an allocation unit.
> 
> [1] https://lore.kernel.org/linux-xfs/0b7f1a4f-da1c-4297-8099-98d738070ab7@huaweicloud.com/
> 
> Signed-off-by: Pankaj Raghav <p.raghav@samsung.com>

Thank you for this test! Some comments below.

> ---
>  common/rc             |   2 +-
>  tests/generic/795     | 172 ++++++++++++++++++++++++++++++++++++++++++
>  tests/generic/795.out |  16 ++++
>  3 files changed, 189 insertions(+), 1 deletion(-)
>  create mode 100755 tests/generic/795
>  create mode 100644 tests/generic/795.out
> 
> diff --git a/common/rc b/common/rc
> index 79189e7e..5e59968f 100644
> --- a/common/rc
> +++ b/common/rc
> @@ -3008,7 +3008,7 @@ _require_xfs_io_command()
>  		testio=`$XFS_IO_PROG -F -f -c "$command $param 0 1m" $testfile 2>&1`
>  		param_checked="$param"
>  		;;
> -	"fpunch" | "fcollapse" | "zero" | "fzero" | "finsert" | "funshare")
> +	"fpunch" | "fcollapse" | "zero" | "fzero" | "finsert" | "funshare" | "fwzero")
>  		local blocksize=$(_get_file_block_size $TEST_DIR)
>  		testio=`$XFS_IO_PROG -F -f -c "pwrite 0 $((5 * $blocksize))" \
>  			-c "fsync" -c "$command $blocksize $((2 * $blocksize))" \
> diff --git a/tests/generic/795 b/tests/generic/795
> new file mode 100755
> index 00000000..2f812f61
> --- /dev/null
> +++ b/tests/generic/795
> @@ -0,0 +1,172 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2026 Samsung Electronics.  All Rights Reserved.
> +#
> +# FS QA Test 795
> +#
> +# Verify FALLOC_FL_WRITE_ZEROES (xfs_io "fwzero") on an unaligned range,
> +# exercising every state the two boundary allocation units can be in.
> +#
> +# WRITE_ZEROES must leave the whole requested range backed by *written*
> +# (zeroed) extents while preserving the out-of-range bytes of the partial
> +# boundary units. The possible scenarios are: written_edges, hole_edges,
> +# unwritten_edges and delalloc_edges.

unwritten_edges needs to distinguish between dirty and clean scenarios.
The expected result for clean should be tmp.zero, while the expected
result for dirty should be tmp.pattern.

> +#
> +# It then covers the EOF cases: a WRITE_ZEROES that *extends* the file to a
> +# non-unit-aligned size, followed by a further extension. WRITE_ZEROES must
> +# never leave written blocks beyond EOF.
> +
> +. ./common/preamble
> +_begin_fstest auto quick prealloc fiemap
> +
> +testfile=$TEST_DIR/$seq.testfile
> +
> +_cleanup()
> +{
> +	cd /
> +	rm -r -f $tmp.*
> +	rm -f $testfile
> +}
> +
> +. ./common/filter
> +. ./common/punch
> +
> +_require_test
> +_require_xfs_io_command "fwzero"
> +_require_xfs_io_command "falloc"
> +_require_xfs_io_command "fiemap"
> +_require_xfs_io_command "truncate"
> +
> +# The allocation unit: rt extent size on an rt config, otherwise the block
> +# size. Deriving the range from this makes the head/tail units straddle rt
> +# extents when rextsize > 1.
> +u=$(_get_file_block_size $TEST_DIR)
> +
> +# The fs block size. On rt with rextsize > 1 the allocation unit u is the rt
> +# extent (u > b). A WRITE_ZEROES that extends EOF must only write out to the
> +# block-rounded EOF and leave the rest of the straddling rt extent unwritten,
> +# rather than written past EOF.
> +b=$(_get_block_size $TEST_DIR)
> +
> +# Unaligned range within EOF: start 1.5 units in, span 4 units, so both the
> +# head unit [u, 2u) and the tail unit [5u, 6u) are only partially covered,
> +# with fully covered units in between and data on both sides.
> +off=$((u + u / 2))
> +len=$((4 * u))
> +filesz=$((8 * u))
> +pattern=0xab
> +
> +# Expected images.  For the written/delalloc cases the pattern surrounds the
> +# zeroed range; for the hole/unwritten cases the file reads back all zeroes.
> +$XFS_IO_PROG -f -c "pwrite -S $pattern 0 $filesz" $tmp.pattern >> $seqres.full 2>&1
> +$XFS_IO_PROG -c "pwrite -S 0 $off $len" $tmp.pattern >> $seqres.full 2>&1
> +$XFS_IO_PROG -f -c "truncate $filesz" $tmp.zero >> $seqres.full 2>&1
> +
> +# Assert the whole requested range is backed by written extents immediately
> +# after fwzero (before any sync/remount): no holes, no unwritten.
> +_check_range_written()
> +{
> +	local file=$1
> +	local bad
> +
> +	bad=$($XFS_IO_PROG -c "fiemap -v $off $len" "$file" | \
> +		_filter_fiemap | grep -E -c 'hole|unwritten')
> +	if [ "$bad" -eq 0 ]; then
> +		echo "extents: range fully written"
> +	else
> +		echo "extents: FAIL - $bad hole/unwritten extent(s) in range"
> +		$XFS_IO_PROG -c "fiemap -v $off $len" "$file" | _filter_fiemap
> +	fi
> +}
> +

The implementation of the fiemap command in xfs_io always adds the
FIEMAP_FLAG_SYNC, which performs a sync operation before querying the
map information, and thus cannot reflect the true result after write
zeroes. For example, if the file system does not accurately convert
delalloc to written after WRITE_ZEROES, this issue will be hidden here.
Perhaps use filefrag -v instead or expand xfs_io?

> +_check_data()
> +{
> +	local file=$1
> +	local want=$2
> +
> +	if cmp -s "$file" "$want"; then
> +		echo "data: matches expected image"
> +	else
> +		echo "data: FAIL - mismatch against expected image"
> +		cmp "$file" "$want" | head
> +	fi
> +}
> +
> +# Assert [start, start+len) has no written extents (unwritten/hole only).  Used
> +# to verify the tail of the straddling rt extent, past the block-rounded EOF,
> +# was left unwritten.  len <= 0 means u == block size (no rt tail) -> nothing to
> +# check, but still emit the line so the output is config-independent.
> +_check_eof_tail()
> +{
> +	local file=$1 start=$2 len=$3 bad
> +
> +	if [ "$len" -le 0 ]; then
> +		echo "extents: eof tail unwritten"
> +		return
> +	fi
> +	bad=$($XFS_IO_PROG -c "fiemap -v $start $len" "$file" | \
> +		_filter_fiemap | grep -E -c 'data')
> +	if [ "$bad" -eq 0 ]; then
> +		echo "extents: eof tail unwritten"
> +	else
> +		echo "extents: FAIL - $bad written extent(s) past block-rounded EOF"
> +		$XFS_IO_PROG -c "fiemap -v $start $len" "$file" | _filter_fiemap
> +	fi
> +}
> +
> +_run_case()
> +{
> +	local name=$1
> +	local want=$2
> +
> +	echo "=== $name ==="
> +
> +	$XFS_IO_PROG -c "fwzero $off $len" $testfile >> $seqres.full 2>&1
> +
> +	echo "== $name fiemap after fwzero ==" >> $seqres.full
> +	$XFS_IO_PROG -c "fiemap -v" $testfile >> $seqres.full 2>&1
> +
> +	_check_range_written $testfile
> +	_test_cycle_mount
> +	_check_data $testfile $want
> +
> +	rm -f $testfile
> +}
> +
> +# 1) Boundary units already written, within i_size.
> +$XFS_IO_PROG -f -c "pwrite -S $pattern 0 $filesz" -c fsync $testfile \
> +	>> $seqres.full 2>&1
> +_run_case "written_edges" $tmp.pattern
> +
> +# 2) Boundary units are holes (sparse file).
> +$XFS_IO_PROG -f -c "truncate $filesz" $testfile >> $seqres.full 2>&1
> +_run_case "hole_edges" $tmp.zero
> +
> +# 3) Boundary units are unwritten preallocation.
> +$XFS_IO_PROG -f -c "falloc 0 $filesz" $testfile >> $seqres.full 2>&1
> +_run_case "unwritten_edges" $tmp.zero
> +
> +# 4) Boundary units are dirty/delalloc:
> +$XFS_IO_PROG -f -c "pwrite -S $pattern 0 $filesz" $testfile >> $seqres.full 2>&1
> +_run_case "delalloc_edges" $tmp.pattern
> +

As mentioned above, add a case: "Boundary units are dirty unwritten"
here.

Thanks,
Yi.

> +# 5) EOF cases. A WRITE_ZEROES that extends the file to a non-unit-aligned
> +#    size, followed by a further extend.
> +eof=$((3 * u + 1))
> +
> +echo "=== eof_then_truncate ==="
> +$XFS_IO_PROG -f -c "fwzero 0 $eof" $testfile >> $seqres.full 2>&1
> +# The write must stop at the block-rounded EOF; the rest of the rt extent that
> +# straddles EOF must be unwritten, not written past EOF.  Round EOF up to the
> +# fs block size (b is a power of two) the same way as generic/219.
> +eof_ru=$(( (eof + b - 1) & ~(b - 1) ))
> +_check_eof_tail $testfile $eof_ru $((4 * u - eof_ru))
> +
> +$XFS_IO_PROG -c "truncate $filesz" $testfile >> $seqres.full 2>&1
> +_test_cycle_mount
> +_check_data $testfile $tmp.zero
> +rm -f $testfile
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/generic/795.out b/tests/generic/795.out
> new file mode 100644
> index 00000000..7183bfa9
> --- /dev/null
> +++ b/tests/generic/795.out
> @@ -0,0 +1,16 @@
> +QA output created by 795
> +=== written_edges ===
> +extents: range fully written
> +data: matches expected image
> +=== hole_edges ===
> +extents: range fully written
> +data: matches expected image
> +=== unwritten_edges ===
> +extents: range fully written
> +data: matches expected image
> +=== delalloc_edges ===
> +extents: range fully written
> +data: matches expected image
> +=== eof_then_truncate ===
> +extents: eof tail unwritten
> +data: matches expected image
> 
> base-commit: ffc8bad17e5b2f56e48dbac43f7c5ae8ac368fe5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
  2026-07-07  3:08 ` Zhang Yi
@ 2026-07-07  5:10   ` Christoph Hellwig
  2026-07-07  6:38     ` Zhang Yi
  0 siblings, 1 reply; 7+ messages in thread
From: Christoph Hellwig @ 2026-07-07  5:10 UTC (permalink / raw)
  To: Zhang Yi
  Cc: Pankaj Raghav, zlang, fstests, hch, djwong, yi.zhang,
	pankaj.raghav, linux-xfs

On Tue, Jul 07, 2026 at 11:08:39AM +0800, Zhang Yi wrote:
> > +# WRITE_ZEROES must leave the whole requested range backed by *written*
> > +# (zeroed) extents while preserving the out-of-range bytes of the partial
> > +# boundary units. The possible scenarios are: written_edges, hole_edges,
> > +# unwritten_edges and delalloc_edges.
> 
> unwritten_edges needs to distinguish between dirty and clean scenarios.
> The expected result for clean should be tmp.zero, while the expected
> result for dirty should be tmp.pattern.

Does it?  I'd expect everything in the range to be zeroed, and everything
outside to be left alone, as the dirty state is just an imlementation
detail not visible to the user.  Similar to other falloc operations,
I'd expect the file system to write back any boundary block first
to make the dirty state difference moot.


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
  2026-07-07  5:10   ` Christoph Hellwig
@ 2026-07-07  6:38     ` Zhang Yi
  2026-07-08 13:21       ` Pankaj Raghav
  0 siblings, 1 reply; 7+ messages in thread
From: Zhang Yi @ 2026-07-07  6:38 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Pankaj Raghav, zlang, fstests, djwong, yi.zhang, pankaj.raghav,
	linux-xfs

On 7/7/2026 1:10 PM, Christoph Hellwig wrote:
> On Tue, Jul 07, 2026 at 11:08:39AM +0800, Zhang Yi wrote:
>>> +# WRITE_ZEROES must leave the whole requested range backed by *written*
>>> +# (zeroed) extents while preserving the out-of-range bytes of the partial
>>> +# boundary units. The possible scenarios are: written_edges, hole_edges,
>>> +# unwritten_edges and delalloc_edges.
>>
>> unwritten_edges needs to distinguish between dirty and clean scenarios.
>> The expected result for clean should be tmp.zero, while the expected
>> result for dirty should be tmp.pattern.
> 
> Does it?  I'd expect everything in the range to be zeroed, and everything
> outside to be left alone, as the dirty state is just an imlementation
> detail not visible to the user.

Yes, although this detail is not perceptible to the user, the final
results of these two scenarios are different.

For clean unwritten extents, the portion beyond the range up to the
aligned block boundary should be zeroed; otherwise, stale data may be
exposed after unwritten-to-written conversion. So the result should
be tmp.zero.

For dirty unwritten extents, all data outside the inode boundary
should be left untouched, so the result should be tmp.pattern.

> Similar to other falloc operations,
> I'd expect the file system to write back any boundary block first
> to make the dirty state difference moot.
> 

In fact, in ext4, for dirty extents (including both dirty delalloc and
dirty unwritten extents), they are not written back first. Instead, the
unaligned boundary blocks are zeroed out directly, followed by a
synchronous write-back before the syscall returns, ensuring the extents
are converted to the written state. This point differs from the behavior
of XFS.

Thanks,
Yi.


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
  2026-07-07  6:38     ` Zhang Yi
@ 2026-07-08 13:21       ` Pankaj Raghav
  2026-07-09  2:32         ` Zhang Yi
  0 siblings, 1 reply; 7+ messages in thread
From: Pankaj Raghav @ 2026-07-08 13:21 UTC (permalink / raw)
  To: Zhang Yi, Christoph Hellwig
  Cc: Pankaj Raghav, zlang, fstests, djwong, yi.zhang, linux-xfs



On 7/7/26 08:38, Zhang Yi wrote:
> On 7/7/2026 1:10 PM, Christoph Hellwig wrote:
>> On Tue, Jul 07, 2026 at 11:08:39AM +0800, Zhang Yi wrote:
>>>> +# WRITE_ZEROES must leave the whole requested range backed by *written*
>>>> +# (zeroed) extents while preserving the out-of-range bytes of the partial
>>>> +# boundary units. The possible scenarios are: written_edges, hole_edges,
>>>> +# unwritten_edges and delalloc_edges.
>>>
>>> unwritten_edges needs to distinguish between dirty and clean scenarios.
>>> The expected result for clean should be tmp.zero, while the expected
>>> result for dirty should be tmp.pattern.
>>
>> Does it?  I'd expect everything in the range to be zeroed, and everything
>> outside to be left alone, as the dirty state is just an imlementation
>> detail not visible to the user.
> 
> Yes, although this detail is not perceptible to the user, the final
> results of these two scenarios are different.
> 
> For clean unwritten extents, the portion beyond the range up to the
> aligned block boundary should be zeroed; otherwise, stale data may be
> exposed after unwritten-to-written conversion. So the result should
> be tmp.zero.
> 
> For dirty unwritten extents, all data outside the inode boundary
> should be left untouched, so the result should be tmp.pattern.
> 
>> Similar to other falloc operations,
>> I'd expect the file system to write back any boundary block first
>> to make the dirty state difference moot.
>>
> 
> In fact, in ext4, for dirty extents (including both dirty delalloc and
> dirty unwritten extents), they are not written back first. Instead, the
> unaligned boundary blocks are zeroed out directly, followed by a
> synchronous write-back before the syscall returns, ensuring the extents
> are converted to the written state. This point differs from the behavior
> of XFS.
> 

I had a similar question as Christoph. But I will include dirty unwritten
test case as well, as ext4 impl may slightly differ.

Thanks for the point about the FIEMAP. I will change it to use filefrag.

I will send the patches soon.

Btw, I tested ext4 and it failed the test. I assume there is a fix on the way for ext4? And did you
also test this for bigalloc configuration? I ran into some issues with bigrtalloc (alloc size >
block size). I am wondering if you hit a similar issue in ext4 for biglloc configs.

--
Pankaj

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
  2026-07-08 13:21       ` Pankaj Raghav
@ 2026-07-09  2:32         ` Zhang Yi
  2026-07-09  7:55           ` Pankaj Raghav
  0 siblings, 1 reply; 7+ messages in thread
From: Zhang Yi @ 2026-07-09  2:32 UTC (permalink / raw)
  To: Pankaj Raghav, Christoph Hellwig
  Cc: Pankaj Raghav, zlang, fstests, djwong, yi.zhang, linux-xfs

On 7/8/2026 9:21 PM, Pankaj Raghav wrote:
> 
> 
> On 7/7/26 08:38, Zhang Yi wrote:
>> On 7/7/2026 1:10 PM, Christoph Hellwig wrote:
>>> On Tue, Jul 07, 2026 at 11:08:39AM +0800, Zhang Yi wrote:
>>>>> +# WRITE_ZEROES must leave the whole requested range backed by *written*
>>>>> +# (zeroed) extents while preserving the out-of-range bytes of the partial
>>>>> +# boundary units. The possible scenarios are: written_edges, hole_edges,
>>>>> +# unwritten_edges and delalloc_edges.
>>>>
>>>> unwritten_edges needs to distinguish between dirty and clean scenarios.
>>>> The expected result for clean should be tmp.zero, while the expected
>>>> result for dirty should be tmp.pattern.
>>>
>>> Does it?  I'd expect everything in the range to be zeroed, and everything
>>> outside to be left alone, as the dirty state is just an imlementation
>>> detail not visible to the user.
>>
>> Yes, although this detail is not perceptible to the user, the final
>> results of these two scenarios are different.
>>
>> For clean unwritten extents, the portion beyond the range up to the
>> aligned block boundary should be zeroed; otherwise, stale data may be
>> exposed after unwritten-to-written conversion. So the result should
>> be tmp.zero.
>>
>> For dirty unwritten extents, all data outside the inode boundary
>> should be left untouched, so the result should be tmp.pattern.
>>
>>> Similar to other falloc operations,
>>> I'd expect the file system to write back any boundary block first
>>> to make the dirty state difference moot.
>>>
>>
>> In fact, in ext4, for dirty extents (including both dirty delalloc and
>> dirty unwritten extents), they are not written back first. Instead, the
>> unaligned boundary blocks are zeroed out directly, followed by a
>> synchronous write-back before the syscall returns, ensuring the extents
>> are converted to the written state. This point differs from the behavior
>> of XFS.
>>
> 
> I had a similar question as Christoph. But I will include dirty unwritten
> test case as well, as ext4 impl may slightly differ.
> 
> Thanks for the point about the FIEMAP. I will change it to use filefrag.
> 
> I will send the patches soon.

Thank you!

> 
> Btw, I tested ext4 and it failed the test. I assume there is a fix on the way for ext4? And did you
> also test this for bigalloc configuration? I ran into some issues with bigrtalloc (alloc size >
> block size). I am wondering if you hit a similar issue in ext4 for biglloc configs.
> 

Yes, there are issues in the current ext4 that need to be fixed, and I
have already sent out the fixed patch set [1]. I have run several
rounds of this test on my machine, including bigalloc scenarios, and
everything looks fine by now.

After applying this patch set, does the issue you encountered still
exist?

Thanks,
Yi.

[1] https://lore.kernel.org/linux-ext4/20260708062049.1982410-1-yi.zhang@huaweicloud.com/


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES
  2026-07-09  2:32         ` Zhang Yi
@ 2026-07-09  7:55           ` Pankaj Raghav
  0 siblings, 0 replies; 7+ messages in thread
From: Pankaj Raghav @ 2026-07-09  7:55 UTC (permalink / raw)
  To: Zhang Yi
  Cc: Pankaj Raghav, zlang, fstests, djwong, yi.zhang, linux-xfs,
	Christoph Hellwig


>>
>> I had a similar question as Christoph. But I will include dirty unwritten
>> test case as well, as ext4 impl may slightly differ.
>>
>> Thanks for the point about the FIEMAP. I will change it to use filefrag.
>>
>> I will send the patches soon.
> 
> Thank you!
> 
>>
>> Btw, I tested ext4 and it failed the test. I assume there is a fix on the way for ext4? And did you
>> also test this for bigalloc configuration? I ran into some issues with bigrtalloc (alloc size >
>> block size). I am wondering if you hit a similar issue in ext4 for biglloc configs.
>>
> 
> Yes, there are issues in the current ext4 that need to be fixed, and I
> have already sent out the fixed patch set [1]. I have run several
> rounds of this test on my machine, including bigalloc scenarios, and
> everything looks fine by now.
> 
> After applying this patch set, does the issue you encountered still
> exist?
> 

I still haven't tried your patchset. I tried running this test on upstream 
kernel and noticed a failure. Then when I saw the ext4 mailing list and there 
were some discussion already ongoing regarding this issue.

I can give your patchset a try as well :)

--
Pankaj


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-07-09  7:56 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-03 12:54 [PATCH] generic/795: add unaligned boundary test cases for WRITE_ZEROES Pankaj Raghav
2026-07-07  3:08 ` Zhang Yi
2026-07-07  5:10   ` Christoph Hellwig
2026-07-07  6:38     ` Zhang Yi
2026-07-08 13:21       ` Pankaj Raghav
2026-07-09  2:32         ` Zhang Yi
2026-07-09  7:55           ` Pankaj Raghav

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.