blob: 8b2c7feff7f8a3ff87f8dbfa3417a35b0024cb86 [file] [log] [blame]
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) Luis Chamberlain. All Rights Reserved.
#
# FS QA Test 749
#
# 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 prealloc
# Import common functions.
. ./common/filter
_require_scratch_nocheck
_require_test
_require_xfs_io_command "truncate"
_require_xfs_io_command "falloc"
# _fixed_by_git_commit kernel <pending-upstream> \
# "filemap: cap PTE range to be created to allowed zero fill in folio_map_range()"
filter_xfs_io_data_unique()
{
_filter_xfs_io_offset | sed -e 's| |\n|g' | grep -E -v "\.|XX|\*" | \
sort -u | tr -d '\n'
}
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
}
mwrite()
{
local file=$1
local offset=$2
local length=$3
local map_len=${4:-$(_round_up_to_page_boundary $(_get_filesize $file)) }
# 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; ulimit -c 0; \
$XFS_IO_PROG $file \
-c 'mmap -w 0 $map_len' \
-c 'mwrite $offset $length'; \
true"
}
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_file_block_size $SCRATCH_MNT)
local failed=0
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
_fail "Block size created ($block_size) doesn't match _get_file_block_size on mount ($fs_block_size)"
fi
rm -f $SCRATCH_MNT/file
# 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. As per mmap(2) subsequent mappings *may* see the modified
# content. This means that it also can get other data and we have
# no rules about what this data should be. Since the data read after
# the actual object data can vary this test just verifies that the
# filesize does not change.
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
let failed=$failed+1
echo "Expected data: $expected_zero_data"
echo " Actual data: $zero_filled_data"
echo "Zero-fill expectations with mmap() not respected"
fi
_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
let failed=$failed+1
echo "Expected csum: $csum_orig"
echo " Actual csum: $csum_post"
echo "mmap() write up to page boundary should not change actual file contents"
fi
local filelen_test=$(_get_filesize $test_file)
if [[ "$filelen_test" != "$new_filelen" ]]; then
let failed=$failed+1
echo "Expected file length: $new_filelen"
echo " Actual file length: $filelen_test"
echo "mmap() write up to page boundary should not change actual file size"
fi
fi
# Now lets ensure we get SIGBUS when we go beyond the page boundary
_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 0 $map_len >> $seqres.full 2>$tmp.err
if grep -q 'Bus error' $tmp.err; then
failed=1
cat $tmp.err
echo "Not expecting SIGBUS when reading up to page boundary"
fi
# This should just work
_mread $test_file 0 $map_len >> $seqres.full 2>$tmp.err
if [[ $? -ne 0 ]]; then
let failed=$failed+1
echo "mmap() read up to page boundary should work"
fi
# This should just work
mwrite $map_len 0 $map_len >> $seqres.full 2>$tmp.err
if [[ $? -ne 0 ]]; then
let failed=$failed+1
echo "mmap() write up to page boundary should work"
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
let failed=$failed+1
echo "mmap() to page boundary works as expected but reading beyond should fail: $mread_err"
fi
$XFS_IO_PROG -w $test_file \
-c "mmap -w 0 $map_len" \
-c "mwrite 0 $((map_len + 10))" >> $seqres.full 2>$tmp.err
local mwrite_err=$?
if [[ $mwrite_err -eq 0 ]]; then
let failed=$failed+1
echo "mmap() to page boundary works as expected but writing beyond should fail: $mwrite_err"
fi
# Now let's go beyond the allowed mmap() page boundary
_mread $test_file 0 $((map_len + 10)) $((map_len + 10)) >> $seqres.full 2>$tmp.err
if ! grep -q 'Bus error' $tmp.err; then
let failed=$failed+1
echo "Expected SIGBUS when mmap() reading beyond page boundary"
fi
mwrite $test_file 0 $((map_len + 10)) $((map_len + 10)) >> $seqres.full 2>$tmp.err
if ! grep -q 'Bus error' $tmp.err; then
let failed=$failed+1
echo "Expected SIGBUS when mmap() writing beyond page boundary"
fi
local filelen_test=$(_get_filesize $test_file)
if [[ "$filelen_test" != "$new_filelen" ]]; then
let failed=$failed+1
echo "Expected file length: $new_filelen"
echo " Actual file length: $filelen_test"
echo "reading or writing beyond file size up to mmap() page boundary should not change file size"
fi
if [[ $failed -eq 1 ]]; then
_fail "Test had $failed failures..."
fi
}
test_block_size()
{
local block_size=$1
do_mmap_tests $block_size 512 3 5
do_mmap_tests $block_size 11k 0 $((4096 * 3 + 3))
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_file_block_size "$SCRATCH_MNT")
test_block_size $block_size
echo "Silence is golden"
status=0
exit