From: "Darrick J. Wong" <djwong@kernel.org>
To: Luis Chamberlain <mcgrof@kernel.org>
Cc: fstests@vger.kernel.org, linux-fsdevel@vger.kernel.org,
willy@infradead.org, p.raghav@samsung.com, da.gomez@samsung.com,
hare@suse.de, john.g.garry@oracle.com, linux-xfs@vger.kernel.org,
patches@lists.linux.dev
Subject: Re: [RFC] fstests: add mmap page boundary tests
Date: Mon, 15 Apr 2024 08:58:25 -0700 [thread overview]
Message-ID: <20240415155825.GC11948@frogsfrogsfrogs> (raw)
In-Reply-To: <20240415081054.1782715-1-mcgrof@kernel.org>
On Mon, Apr 15, 2024 at 01:10:54AM -0700, Luis Chamberlain wrote:
> mmap() POSIX compliance says we should zero fill data beyond a file
> size up to page boundary, and issue a SIGBUS if we go beyond. While fsx
> helps us test zero-fill sometimes, fsstress also let's us sometimes test
> for SIGBUS however that is based on a random value and its not likley we
> always test it. Dedicate a specic test for this to make testing for
> this specific situation and to easily expand on other corner cases.
>
> Suggested-by: Matthew Wilcox <willy@infradead.org>
> Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
> ---
>
> Does enough to get us to use to test this, however I'm aware of a bit
> more polishing up to do:
>
> * maybe moving mread to common as generic/574 did it first
> * sharing round_up_to_page_boundary() as well
>
> generic/574 is special, it was just testing for correctness of
> integrity if we muck with mmap() however if you don't have verity
> stuff available obviously you won't end up testing it.
>
> This generalizes mmap() zero-fill and SIGBUS corner case tests.
>
> I've tested so far only 4k and it works well there. For 16k bs on LBS
> just the SIGBUS issue exists, I'll test smaller block sizes later like
> 512, 1k, 2k as well. We'll fix triggering the SIBGUS when LBS is used,
> we'll address that in the next iteration.
>
> Is this a worthy test as a generic test?
>
> common/filter | 6 ++
> tests/generic/740 | 231 ++++++++++++++++++++++++++++++++++++++++++
> tests/generic/740.out | 2 +
> 3 files changed, 239 insertions(+)
> create mode 100755 tests/generic/740
> create mode 100644 tests/generic/740.out
>
> diff --git a/common/filter b/common/filter
> index 36d51bd957dd..d7add06f3be7 100644
> --- a/common/filter
> +++ b/common/filter
> @@ -194,6 +194,12 @@ _filter_xfs_io_unique()
> common_line_filter | _filter_xfs_io
> }
>
> +_filter_xfs_io_data_unique()
> +{
> + _filter_xfs_io_offset | sed -e 's| |\n|g' | egrep -v "\.|XX|\*" | \
> + sort | uniq | tr -d '\n'
> +}
> +
> _filter_xfs_io_units_modified()
> {
> UNIT=$1
> diff --git a/tests/generic/740 b/tests/generic/740
> new file mode 100755
> index 000000000000..cbb823014600
> --- /dev/null
> +++ b/tests/generic/740
> @@ -0,0 +1,231 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) Luis Chamberlain. All Rights Reserved.
> +#
> +# FS QA Test 740
> +#
> +# As per POSIX NOTES mmap(2) maps multiples of the system page size, but if the
> +# data mapped is not multiples of the page size the remaining bytes are zeroed
> +# out when mapped and modifications to that region are not written to the file.
> +# On Linux when you write data to such partial page after the end of the
> +# object, the data stays in the page cache even after the file is closed and
> +# unmapped and even though the data is never written to the file itself,
> +# subsequent mappings may see the modified content. If you go *beyond* this
> +# page, you should get a SIGBUS. This test verifies we zero-fill to page
> +# boundary and ensures we get a SIGBUS if we write to data beyond the system
> +# page size even if the block size is greater than the system page size.
> +. ./common/preamble
> +. ./common/rc
> +_begin_fstest auto quick
> +
> +# Override the default cleanup function.
> +_cleanup()
> +{
> + cd /
> + rm -r -f $tmp.*
> +}
> +
> +# Import common functions.
> +. ./common/filter
> +
> +# real QA test starts here
> +_supported_fs generic
> +_require_scratch_nocheck
> +_require_test
> +
> +setup_zeroed_file()
> +{
> + local file_len=$1
> + local sparse=$2
> +
> + if $sparse; then
> + $XFS_IO_PROG -f -c "truncate $file_len" $test_file
> + else
> + $XFS_IO_PROG -f -c "falloc 0 $file_len" $test_file
> + fi
> +}
> +
> +round_up_to_page_boundary()
> +{
> + local n=$1
> + local page_size=$(_get_page_size)
> +
> + echo $(( (n + page_size - 1) & ~(page_size - 1) ))
Does iomap put a large folio into the pagecache that crosses EOF, or
does it back down to base page size?
> +}
> +
> +mread()
> +{
> + local file=$1
> + local map_len=$2
> + local offset=$3
> + local length=$4
> +
> + # Some callers expect xfs_io to crash with SIGBUS due to the mread,
> + # causing the shell to print "Bus error" to stderr. To allow this
> + # message to be redirected, execute xfs_io in a new shell instance.
> + # However, for this to work reliably, we also need to prevent the new
> + # shell instance from optimizing out the fork and directly exec'ing
> + # xfs_io. The easiest way to do that is to append 'true' to the
> + # commands, so that xfs_io is no longer the last command the shell sees.
> + bash -c "trap '' SIGBUS; $XFS_IO_PROG -r $file \
> + -c 'mmap -r 0 $map_len' \
> + -c 'mread $offset $length'; true"
Please hoist the mread() function with generic/574 to common; your copy
is out of date with the original.
> +}
> +
> +do_mmap_tests()
> +{
> + local block_size=$1
> + local file_len=$2
> + local offset=$3
> + local len=$4
> + local use_sparse_file=${5:-false}
> + local new_filelen=0
> + local map_len=0
> + local csum=0
> + local fs_block_size=$(_get_block_size $SCRATCH_MNT)
> +
> + echo -en "\n\n==> Testing blocksize $block_size " >> $seqres.full
> + echo -en "file_len: $file_len offset: $offset " >> $seqres.full
> + echo -e "len: $len sparse: $use_sparse_file" >> $seqres.full
> +
> + if ((fs_block_size != block_size)); then
> + echo "Block size created ($block_size) doesn't match _get_block_size on mount ($fs_block_size)"
> + _fail
_fail "Block size created..."
> + fi
> +
> + rm -rf "${SCRATCH_MNT:?}"/*
> +
> + # This let's us also test against sparse files
> + setup_zeroed_file $file_len $use_sparse_file
> +
> + # This will overwrite the old data, the file size is the
> + # delta between offset and len now.
> + $XFS_IO_PROG -f -c "pwrite -S 0xaa -b 512 $offset $len" \
> + $test_file >> $seqres.full
> +
> + sync
> + new_filelen=$(_get_filesize $test_file)
> + map_len=$(round_up_to_page_boundary $new_filelen)
> + csum_orig="$(_md5_checksum $test_file)"
> +
> + # A couple of mmap() tests:
> + #
> + # We are allowed to mmap() up to the boundary of the page size of a
> + # data object, but there a few rules to follow we must check for:
> + #
> + # a) zero-fill test for the data: POSIX says we should zero fill any
> + # partial page after the end of the object. Verify zero-fill.
> + # b) do not write this bogus data to disk: on Linux, if we write data
> + # to a partially filled page, it will stay in the page cache even
> + # after the file is closed and unmapped even if it never reaches the
> + # file. Subsequent mappings *may* see the modified content, but it
> + # also can get other data. Since the data read after the actual
What does "other data" mean?
> + # object data can vary we just verify the filesize does not change.
> + # This is not true for tmpfs.
Er... is this file size change a bug?
> + if [[ $map_len -gt $new_filelen ]]; then
> + zero_filled_data_len=$((map_len - new_filelen))
> + _scratch_cycle_mount
> + expected_zero_data="00"
> + zero_filled_data=$($XFS_IO_PROG -r $test_file \
> + -c "mmap -r 0 $map_len" \
> + -c "mread -v $new_filelen $zero_filled_data_len" \
> + -c "munmap" | \
> + _filter_xfs_io_data_unique)
> + if [[ "$zero_filled_data" != "$expected_zero_data" ]]; then
> + echo "Expected data: $expected_zero_data"
> + echo " Actual data: $zero_filled_data"
> + echo "Zero-fill broken see mmap() requirements"
> + _fail
> + fi
> +
> + if [[ "$FSTYP" != "tmpfs" ]]; then
> + _scratch_cycle_mount
> + $XFS_IO_PROG $test_file \
> + -c "mmap -w 0 $map_len" \
> + -c "mwrite $new_filelen $zero_filled_data_len" \
> + -c "munmap"
> + sync
> + csum_post="$(_md5_checksum $test_file)"
> + if [[ "$csum_orig" != "$csum_post" ]]; then
> + echo "Expected csum: $csum_orig"
> + echo " Actual csum: $csum_post"
> + _fail
> + fi
> +
> + local filelen_test=$(_get_filesize $test_file)
> + if [[ "$filelen_test" != "$new_filelen" ]]; then
> + echo "Expected file length: $new_filelen"
> + echo " Actual file length: $filelen_test"
> + _fail
> + fi
> + fi
> + fi
> +
> + # Now lets ensure we get SIGBUS when we go beyond the page boundary
> + if [[ "$FSTYP" != "tmpfs" ]]; then
> + _scratch_cycle_mount
> + new_filelen=$(_get_filesize $test_file)
> + map_len=$(round_up_to_page_boundary $new_filelen)
> + csum_orig="$(_md5_checksum $test_file)"
> + mread $test_file $map_len 0 $map_len >> $seqres.full 2>$tmp.err
> + if grep -q 'Bus error' $tmp.err; then
> + echo "Not expecting SIGBUS when reading up to page boundary"
> + cat $tmp.err
> + _fail
> + fi
> +
> + # This should just work
> + mread $test_file $map_len 0 $map_len >> $seqres.full 2>$tmp.err
> + if [[ $? -ne 0 ]]; then
> + _fail
> + fi
> +
> + # If we mmap() on the boundary but try to read beyond it just
> + # fails, we don't get a SIGBUS
> + $XFS_IO_PROG -r $test_file \
> + -c "mmap -r 0 $map_len" \
> + -c "mread 0 $((map_len + 10))" >> $seqres.full 2>$tmp.err
> + local mread_err=$?
> + if [[ $mread_err -eq 0 ]]; then
> + echo "mmap() to page boundary works as expected but reading beyond should fail"
> + echo "err: $?"
> + _fail
> + fi
> +
> + # Now let's go beyond the allowed mmap() page boundary
> + mread $test_file $((map_len + 10)) 0 $((map_len + 10)) >> $seqres.full 2>$tmp.err
> + if ! grep -q 'Bus error' $tmp.err; then
> + echo "Expected SIGBUS when mmap() reading beyond page boundary"
> + _fail
> + fi
> + local filelen_test=$(_get_filesize $test_file)
> + if [[ "$filelen_test" != "$new_filelen" ]]; then
> + echo "Expected file length: $new_filelen"
> + echo " Actual file length: $filelen_test"
> + _fail
> + fi
> + fi
> +}
> +
> +test_block_size()
> +{
> + local block_size=$1
> +
> + do_mmap_tests $block_size 512 3 5
> + do_mmap_tests $block_size 16k 0 $((16384+3))
> + do_mmap_tests $block_size 16k $((16384-10)) $((16384+20))
> + do_mmap_tests $block_size 64k 0 $((65536+3))
> + do_mmap_tests $block_size 4k 4090 30 true
> +}
> +
> +_scratch_mkfs >> $seqres.full 2>&1 || _fail "mkfs failed"
> +_scratch_mount
> +test_file=$SCRATCH_MNT/file
> +block_size=$(_get_block_size "$SCRATCH_MNT")
_get_file_block_size ?
> +test_block_size $block_size
> +_scratch_unmount
> +_check_scratch_fs
This work is done for you automatically if you simply exit the test
with the scratch fs still mounted.
--D
> +
> +echo "Silence is golden"
> +status=0
> +exit
> diff --git a/tests/generic/740.out b/tests/generic/740.out
> new file mode 100644
> index 000000000000..3f841e600ed3
> --- /dev/null
> +++ b/tests/generic/740.out
> @@ -0,0 +1,2 @@
> +QA output created by 740
> +Silence is golden
> --
> 2.43.0
>
>
next prev parent reply other threads:[~2024-04-15 15:58 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-15 8:10 [RFC] fstests: add mmap page boundary tests Luis Chamberlain
2024-04-15 15:58 ` Darrick J. Wong [this message]
2024-04-22 14:09 ` Pankaj Raghav (Samsung)
2024-05-28 22:18 ` Luis Chamberlain
2024-06-05 4:40 ` Luis Chamberlain
2024-04-15 20:05 ` Zorro Lang
2024-05-28 22:23 ` Luis Chamberlain
2024-04-22 14:12 ` Pankaj Raghav (Samsung)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240415155825.GC11948@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=da.gomez@samsung.com \
--cc=fstests@vger.kernel.org \
--cc=hare@suse.de \
--cc=john.g.garry@oracle.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-xfs@vger.kernel.org \
--cc=mcgrof@kernel.org \
--cc=p.raghav@samsung.com \
--cc=patches@lists.linux.dev \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox