| #! /bin/bash |
| # FS QA Test No. 298 |
| # |
| # Test that filesystem sends discard requests only on free blocks |
| # |
| #----------------------------------------------------------------------- |
| # Copyright (c) 2013 Red Hat, Inc., Tomas Racek <tracek@redhat.com> |
| # |
| # This program is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU General Public License as |
| # published by the Free Software Foundation. |
| # |
| # This program is distributed in the hope that it would be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write the Free Software Foundation, |
| # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| #----------------------------------------------------------------------- |
| # |
| |
| 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 |
| _supported_os Linux |
| _require_test |
| _require_loop |
| _require_fstrim |
| _require_xfs_io_command "fiemap" |
| _require_fs_space $TEST_DIR 307200 |
| [ "$FSTYP" = "ext4" ] && _require_dumpe2fs |
| |
| _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() |
| { |
| $XFS_IO_PROG -F -c fiemap $1 | grep hole | $SED_PROG 's/.*\[\(.*\)\.\.\(.*\)\].*/\1 \2/' |
| } |
| |
| get_free_sectors() |
| { |
| case $FSTYP in |
| ext4) |
| $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 $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 }' |
| ;; |
| 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=300 &> /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" |
| |
| _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!" |
| exit |
| fi |
| done < $fiemap_after |
| echo "done." |
| |
| status=0 |
| exit |