Re: [PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES
Zhang Yi <[email protected]> Mon, 20 Jul 2026 19:10:08 +0800
| Newsgroups | org.kernel.vger.fstests,org.kernel.vger.linux-xfs |
|---|---|
| Message-ID | <[email protected]> |
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/[email protected]/ > > Signed-off-by: Pankaj Raghav <[email protected]> Looks good to me. Reviewed-by: Zhang Yi <[email protected]> > --- > 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