* [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES
@ 2026-07-17 6:42 Pankaj Raghav
2026-07-20 9:18 ` Christoph Hellwig
2026-07-20 11:10 ` Zhang Yi
0 siblings, 2 replies; 3+ messages in thread
From: Pankaj Raghav @ 2026-07-17 6:42 UTC (permalink / raw)
To: zlang
Cc: linux-xfs, fstests, djwong, Zhang Yi, pankaj.raghav, hch,
yi.zhang, 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>
---
Changes since v2:
- Move back to fiemap with the newly added '-e' option.
Changes since v1:
- Use filefrag instead of FIEMAP as the latter uses FIEMAP_FLAG_SYNC by
default when used via xfs_io (Zhang yi)
- Add an extra testcase for dirty-unwritten edges (Zhang Yi)
common/rc | 2 +-
tests/generic/795 | 177 ++++++++++++++++++++++++++++++++++++++++++
tests/generic/795.out | 19 +++++
3 files changed, 197 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..964a8f9a
--- /dev/null
+++ b/tests/generic/795
@@ -0,0 +1,177 @@
+#! /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" "-e"
+_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) Boundary units are dirty unwritten.
+$XFS_IO_PROG -f -c "falloc 0 $filesz" -c "pwrite -S $pattern 0 $filesz" \
+ $testfile >> $seqres.full 2>&1
+_run_case "dirty_unwritten_edges" $tmp.pattern
+
+# 6) 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..14667aef
--- /dev/null
+++ b/tests/generic/795.out
@@ -0,0 +1,19 @@
+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
+=== dirty_unwritten_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] 3+ messages in thread
* Re: [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES
2026-07-17 6:42 [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES Pankaj Raghav
@ 2026-07-20 9:18 ` Christoph Hellwig
2026-07-20 11:10 ` Zhang Yi
1 sibling, 0 replies; 3+ messages in thread
From: Christoph Hellwig @ 2026-07-20 9:18 UTC (permalink / raw)
To: Pankaj Raghav
Cc: zlang, linux-xfs, fstests, djwong, Zhang Yi, pankaj.raghav, hch,
yi.zhang
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES
2026-07-17 6:42 [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES Pankaj Raghav
2026-07-20 9:18 ` Christoph Hellwig
@ 2026-07-20 11:10 ` Zhang Yi
1 sibling, 0 replies; 3+ messages in thread
From: Zhang Yi @ 2026-07-20 11:10 UTC (permalink / raw)
To: Pankaj Raghav, zlang
Cc: linux-xfs, fstests, djwong, pankaj.raghav, hch, yi.zhang
On 7/17/2026 2:42 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>
Looks good to me.
Reviewed-by: Zhang Yi <yi.zhang@huawei.com>
> ---
> Changes since v2:
> - Move back to fiemap with the newly added '-e' option.
>
> Changes since v1:
> - Use filefrag instead of FIEMAP as the latter uses FIEMAP_FLAG_SYNC by
> default when used via xfs_io (Zhang yi)
> - Add an extra testcase for dirty-unwritten edges (Zhang Yi)
>
> common/rc | 2 +-
> tests/generic/795 | 177 ++++++++++++++++++++++++++++++++++++++++++
> tests/generic/795.out | 19 +++++
> 3 files changed, 197 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..964a8f9a
> --- /dev/null
> +++ b/tests/generic/795
> @@ -0,0 +1,177 @@
> +#! /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" "-e"
> +_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) Boundary units are dirty unwritten.
> +$XFS_IO_PROG -f -c "falloc 0 $filesz" -c "pwrite -S $pattern 0 $filesz" \
> + $testfile >> $seqres.full 2>&1
> +_run_case "dirty_unwritten_edges" $tmp.pattern
> +
> +# 6) 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..14667aef
> --- /dev/null
> +++ b/tests/generic/795.out
> @@ -0,0 +1,19 @@
> +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
> +=== dirty_unwritten_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] 3+ messages in thread
end of thread, other threads:[~2026-07-20 11:10 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 6:42 [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES Pankaj Raghav
2026-07-20 9:18 ` Christoph Hellwig
2026-07-20 11:10 ` Zhang Yi
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.