#! /bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2013 Red Hat, Inc., Tomas Racek # # 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 # remove previous $seqres.full before test rm -f $seqres.full _supported_fs ext4 xfs btrfs _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