Files
apfstests/tests/shared/298
T
Darrick J. Wong 6f157d593a shared/298: unmount filesystem before examining underlying storage
This test does some weird things with live filesystems -- it seems to be
validating the behavior of fstrim by comparing the filesystem's free
space map to holes in the file image that backs the filesystem.
However, this doesn't account for the fact that some filesystems
maintain in-core preallocations and/or can perturb the free space data
during unmount.  This causes sporadic test failures when the two become
out of sync.

Therefore, make sure we unmount the filesystem before we start running
tools against the filesystem image file to eliminate the possibility of
changes to the free space map.  This was found by running shared/298 on
xfs with a 1k block size.

cc: enwlinux@gmail.com
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
2019-03-07 12:43:54 +08:00

223 lines
5.7 KiB
Bash
Executable File

#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2013 Red Hat, Inc., Tomas Racek <tracek@redhat.com>
#
# FS QA Test No. 298
#
# Test that filesystem sends discard requests only on free blocks
#
seq=`basename $0`
seqres=$RESULT_DIR/$seq
echo "QA output created by $seq"
status=1 # failure is the default!
trap "_cleanup; exit \$status" 0 1 2 3 15
. ./common/rc
_supported_fs ext4 xfs btrfs
_supported_os Linux
_require_test
_require_loop
_require_fstrim
_require_xfs_io_command "fiemap"
if [ "$FSTYP" = "btrfs" ]; then
# 3g for btrfs to have distinct bgs
_require_fs_space $TEST_DIR 3145728
fssize=3000
else
_require_fs_space $TEST_DIR 307200
fssize=300
fi
[ "$FSTYP" = "ext4" ] && _require_dumpe2fs
[ "$FSTYP" = "btrfs" ] && _require_btrfs_command inspect-internal dump-super
[ "$FSTYP" = "btrfs" ] && _require_btrfs_command inspect-internal dump-tree
_cleanup()
{
$UMOUNT_PROG $loop_dev &> /dev/null
_destroy_loop_device $loop_dev
if [ $status -eq 0 ]; then
rm -rf $tmp
rm $img_file
fi
}
get_holes()
{
# It's not a good idea to be running tools against the image file
# backing a live filesystem because the filesystem could be maintaining
# in-core state that will perturb the free space map on umount. Stick
# to established convention which requires the filesystem to be
# unmounted while we probe the underlying file.
$UMOUNT_PROG $loop_mnt
$XFS_IO_PROG -F -c fiemap $1 | grep hole | $SED_PROG 's/.*\[\(.*\)\.\.\(.*\)\].*/\1 \2/'
_mount $loop_dev $loop_mnt
}
get_free_sectors()
{
case $FSTYP in
ext4)
$UMOUNT_PROG $loop_mnt
$DUMPE2FS_PROG $img_file 2>&1 | grep " Free blocks" | cut -d ":" -f2- | \
tr ',' '\n' | $SED_PROG 's/^ //' | \
$AWK_PROG -v spb=$sectors_per_block 'BEGIN{FS="-"};
NF {
if($2 != "") # range of blocks
print spb * $1, spb * ($2 + 1) - 1;
else # just single block
print spb * $1, spb * ($1 + 1) - 1;
}'
;;
xfs)
agsize=`$XFS_INFO_PROG $loop_mnt | $SED_PROG -n 's/.*agsize=\(.*\) blks.*/\1/p'`
# Convert free space (agno, block, length) to (start sector, end sector)
$UMOUNT_PROG $loop_mnt
$XFS_DB_PROG -r -c "freesp -d" $img_file | $SED_PROG '/^.*from/,$d'| \
$AWK_PROG -v spb=$sectors_per_block -v agsize=$agsize \
'{ print spb * ($1 * agsize + $2), spb * ($1 * agsize + $2 + $3) - 1 }'
;;
btrfs)
local device_size=$($BTRFS_UTIL_PROG filesystem show --raw $loop_mnt 2>&1 \
| sed -n "s/^.*size \([0-9]*\).*$/\1/p")
local nodesize=$($BTRFS_UTIL_PROG inspect-internal dump-super $img_file \
| sed -n 's/nodesize\s*\(.*\)/\1/p')
# Get holes within block groups
$BTRFS_UTIL_PROG inspect-internal dump-tree -t extent $img_file \
| $AWK_PROG -v sectorsize=512 -v nodesize=$nodesize -f $here/src/parse-extent-tree.awk
# Get holes within unallocated space on disk
$BTRFS_UTIL_PROG inspect-internal dump-tree -t dev $img_file \
| $AWK_PROG -v sectorsize=512 -v devsize=$device_size -f $here/src/parse-dev-tree.awk
;;
esac
}
merge_ranges()
{
# Merges consecutive ranges from two input files
file1=$1
file2=$2
tmp_file=$tmp/sectors.tmp
cat $file1 $file2 | sort -n > $tmp_file
read line < $tmp_file
start=${line% *}
end=${line#* }
# Continue from second line
sed -i "1d" $tmp_file
while read line; do
curr_start=${line% *}
curr_end=${line#* }
if [ `expr $end + 1` -ge $curr_start ]; then
if [ $curr_end -gt $end ]; then
end=$curr_end
fi
else
echo $start $end
start=$curr_start
end=$curr_end
fi
done < $tmp_file
# Print last line
echo $start $end
rm $tmp_file
}
here=`pwd`
tmp=`mktemp -d`
img_file=$TEST_DIR/$$.fs
dd if=/dev/zero of=$img_file bs=1M count=$fssize &> /dev/null
loop_dev=$(_create_loop_device $img_file)
loop_mnt=$tmp/loop_mnt
fiemap_ref="$tmp/reference"
fiemap_after="$tmp/after"
free_sectors="$tmp/free_sectors"
merged_sectors="$tmp/merged_free_sectors"
mkdir $loop_mnt
[ "$FSTYP" = "xfs" ] && MKFS_OPTIONS="-f $MKFS_OPTIONS"
[ "$FSTYP" = "btrfs" ] && MKFS_OPTIONS="$MKFS_OPTIONS -f -dsingle -msingle"
_mkfs_dev $loop_dev
_mount $loop_dev $loop_mnt
echo -n "Generating garbage on loop..."
# Goal is to fill it up, ignore any errors.
for i in `seq 1 10`; do
mkdir $loop_mnt/$i &> /dev/null
cp -r $here/* $loop_mnt/$i &> /dev/null || break
done
# Get reference fiemap, this can contain i.e. uninitialized inode table
sync
get_holes $img_file > $fiemap_ref
# Delete some files
find $loop_mnt -type f -print | $AWK_PROG \
'BEGIN {srand()}; {if(rand() > 0.7) print $1;}' | xargs rm
echo "done."
echo -n "Running fstrim..."
$FSTRIM_PROG $loop_mnt &> /dev/null
echo "done."
echo -n "Detecting interesting holes in image..."
# Get after-trim fiemap
sync
get_holes $img_file > $fiemap_after
echo "done."
echo -n "Comparing holes to the reported space from FS..."
# Get block size
block_size=$(_get_block_size $loop_mnt/)
sectors_per_block=`expr $block_size / 512`
# Obtain free space from filesystem
get_free_sectors > $free_sectors
# Merge original holes with free sectors
merge_ranges $fiemap_ref $free_sectors > $merged_sectors
# Check that all holes after fstrim call were already present before or
# that they match free space reported from FS
while read line; do
from=${line% *}
to=${line#* }
if ! $AWK_PROG -v s=$from -v e=$to \
'{ if ($1 <= s && e <= $2) found = 1};
END { if(found) exit 0; else exit 1}' $merged_sectors
then
echo "Sectors $from-$to are not marked as free!"
# Dump the state to make it easier to debug this...
echo free_sectors >> $seqres.full
sort -g < $free_sectors >> $seqres.full
echo fiemap_ref >> $seqres.full
sort -g < $fiemap_ref >> $seqres.full
echo merged_sectors >> $seqres.full
sort -g < $merged_sectors >> $seqres.full
echo fiemap_after >> $seqres.full
sort -g < $fiemap_after >> $seqres.full
exit
fi
done < $fiemap_after
echo "done."
status=0
exit