[PATCH v3] generic/795: add unaligned boundary test cases for WRITE_ZEROES
Pankaj Raghav <[email protected]> Fri, 17 Jul 2026 08:42:16 +0200
| Newsgroups | org.kernel.vger.fstests,org.kernel.vger.linux-xfs |
|---|---|
| Message-ID | <[email protected]> |
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]> --- 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