common/rc: add _scratch_{u}mount_idmapped() helpers
[xfstests-dev.git] / tests / shared / 298
1 #! /bin/bash
2 # SPDX-License-Identifier: GPL-2.0
3 # Copyright (c) 2013 Red Hat, Inc., Tomas Racek <tracek@redhat.com>
4 #
5 # FS QA Test No. 298
6 #
7 # Test that filesystem sends discard requests only on free blocks
8 #
9 seq=`basename $0`
10 seqres=$RESULT_DIR/$seq
11 echo "QA output created by $seq"
12
13 status=1        # failure is the default!
14 trap "_cleanup; exit \$status" 0 1 2 3 15
15
16 . ./common/rc
17
18 # remove previous $seqres.full before test
19 rm -f $seqres.full
20
21 _supported_fs ext4 xfs btrfs
22 _require_test
23 _require_loop
24 _require_fstrim
25 _require_xfs_io_command "fiemap"
26 if [ "$FSTYP" = "btrfs" ]; then
27         # 3g for btrfs to have distinct bgs
28         _require_fs_space $TEST_DIR 3145728
29         fssize=3000
30 else
31         _require_fs_space $TEST_DIR 307200
32         fssize=300
33 fi
34
35 [ "$FSTYP" = "ext4" ] && _require_dumpe2fs
36 [ "$FSTYP" = "btrfs" ] && _require_btrfs_command inspect-internal dump-super
37 [ "$FSTYP" = "btrfs" ] && _require_btrfs_command inspect-internal dump-tree
38
39 _cleanup()
40 {
41         $UMOUNT_PROG $loop_dev &> /dev/null
42         _destroy_loop_device $loop_dev
43         if [ $status -eq 0 ]; then
44                 rm -rf $tmp
45                 rm $img_file
46         fi
47 }
48
49 get_holes()
50 {
51         # It's not a good idea to be running tools against the image file
52         # backing a live filesystem because the filesystem could be maintaining
53         # in-core state that will perturb the free space map on umount.  Stick
54         # to established convention which requires the filesystem to be
55         # unmounted while we probe the underlying file.
56         $UMOUNT_PROG $loop_mnt
57         $XFS_IO_PROG -F -c fiemap $1 | grep hole | $SED_PROG 's/.*\[\(.*\)\.\.\(.*\)\].*/\1 \2/'
58         _mount $loop_dev $loop_mnt
59 }
60
61 get_free_sectors()
62 {
63         case $FSTYP in
64         ext4)
65         $UMOUNT_PROG $loop_mnt
66         $DUMPE2FS_PROG $img_file  2>&1 | grep " Free blocks" | cut -d ":" -f2- | \
67                 tr ',' '\n' | $SED_PROG 's/^ //' | \
68                 $AWK_PROG -v spb=$sectors_per_block 'BEGIN{FS="-"};
69                      NF {
70                         if($2 != "") # range of blocks
71                                 print spb * $1, spb * ($2 + 1) - 1;
72                         else            # just single block
73                                 print spb * $1, spb * ($1 + 1) - 1;
74                      }'
75         ;;
76         xfs)
77         agsize=`$XFS_INFO_PROG $loop_mnt | $SED_PROG -n 's/.*agsize=\(.*\) blks.*/\1/p'`
78         # Convert free space (agno, block, length) to (start sector, end sector)
79         $UMOUNT_PROG $loop_mnt
80         $XFS_DB_PROG -r -c "freesp -d" $img_file | $SED_PROG '/^.*from/,$d'| \
81                  $AWK_PROG -v spb=$sectors_per_block -v agsize=$agsize \
82                 '{ print spb * ($1 * agsize + $2), spb * ($1 * agsize + $2 + $3) - 1 }'
83         ;;
84         btrfs)
85                 local device_size=$($BTRFS_UTIL_PROG filesystem show --raw $loop_mnt 2>&1 \
86                         | sed -n "s/^.*size \([0-9]*\).*$/\1/p")
87
88                 local nodesize=$($BTRFS_UTIL_PROG inspect-internal dump-super $img_file  \
89                         | sed -n 's/nodesize\s*\(.*\)/\1/p')
90
91                 # Get holes within block groups
92                 $BTRFS_UTIL_PROG inspect-internal dump-tree -t extent $img_file \
93                         | $AWK_PROG -v sectorsize=512 -v nodesize=$nodesize -f $here/src/parse-extent-tree.awk
94
95                 # Get holes within unallocated space on disk
96                 $BTRFS_UTIL_PROG inspect-internal dump-tree -t dev $img_file \
97                         | $AWK_PROG -v sectorsize=512 -v devsize=$device_size -f $here/src/parse-dev-tree.awk
98
99         ;;
100         esac
101 }
102
103 merge_ranges()
104 {
105         # Merges consecutive ranges from two input files
106         file1=$1
107         file2=$2
108
109         tmp_file=$tmp/sectors.tmp
110
111         cat $file1 $file2 | sort -n > $tmp_file
112
113         read line < $tmp_file
114         start=${line% *}
115         end=${line#* }
116
117         # Continue from second line
118         sed -i "1d" $tmp_file
119         while read line; do
120                 curr_start=${line% *}
121                 curr_end=${line#* }
122
123                 if [ `expr $end + 1` -ge $curr_start ]; then
124                         if [ $curr_end -gt $end ]; then
125                                 end=$curr_end
126                         fi
127                 else
128                         echo $start $end
129                         start=$curr_start
130                         end=$curr_end
131                 fi
132         done < $tmp_file
133
134         # Print last line
135         echo $start $end
136
137         rm $tmp_file
138 }
139
140 here=`pwd`
141 tmp=`mktemp -d`
142
143 img_file=$TEST_DIR/$$.fs
144 dd if=/dev/zero of=$img_file bs=1M count=$fssize &> /dev/null
145
146 loop_dev=$(_create_loop_device $img_file)
147 loop_mnt=$tmp/loop_mnt
148
149 fiemap_ref="$tmp/reference"
150 fiemap_after="$tmp/after"
151 free_sectors="$tmp/free_sectors"
152 merged_sectors="$tmp/merged_free_sectors"
153
154 mkdir $loop_mnt
155
156 [ "$FSTYP" = "xfs" ] && MKFS_OPTIONS="-f $MKFS_OPTIONS"
157 [ "$FSTYP" = "btrfs" ] && MKFS_OPTIONS="$MKFS_OPTIONS -f -dsingle -msingle"
158
159 _mkfs_dev $loop_dev
160 _mount $loop_dev $loop_mnt
161
162 echo -n "Generating garbage on loop..."
163 # Goal is to fill it up, ignore any errors.
164 for i in `seq 1 10`; do
165         mkdir $loop_mnt/$i &> /dev/null
166         cp -r $here/* $loop_mnt/$i &> /dev/null || break
167 done
168
169 # Get reference fiemap, this can contain i.e. uninitialized inode table
170 sync
171 get_holes $img_file > $fiemap_ref
172
173 # Delete some files
174 find $loop_mnt -type f -print | $AWK_PROG \
175         'BEGIN {srand()}; {if(rand() > 0.7) print $1;}' | xargs rm
176 echo "done."
177
178 echo -n "Running fstrim..."
179 $FSTRIM_PROG $loop_mnt &> /dev/null
180 echo "done."
181
182 echo -n "Detecting interesting holes in image..."
183 # Get after-trim fiemap
184 sync
185 get_holes $img_file > $fiemap_after
186 echo "done."
187
188 echo -n "Comparing holes to the reported space from FS..."
189 # Get block size
190 block_size=$(_get_block_size $loop_mnt/)
191 sectors_per_block=`expr $block_size / 512`
192
193 # Obtain free space from filesystem
194 get_free_sectors > $free_sectors
195 # Merge original holes with free sectors
196 merge_ranges $fiemap_ref $free_sectors > $merged_sectors
197
198 # Check that all holes after fstrim call were already present before or
199 # that they match free space reported from FS
200 while read line; do
201         from=${line% *}
202         to=${line#* }
203         if ! $AWK_PROG -v s=$from -v e=$to \
204                 '{ if ($1 <= s && e <= $2) found = 1};
205                 END { if(found) exit 0; else exit 1}' $merged_sectors
206         then
207                 echo "Sectors $from-$to are not marked as free!"
208
209                 # Dump the state to make it easier to debug this...
210                 echo free_sectors >> $seqres.full
211                 sort -g < $free_sectors >> $seqres.full
212                 echo fiemap_ref >> $seqres.full
213                 sort -g < $fiemap_ref >> $seqres.full
214                 echo merged_sectors >> $seqres.full
215                 sort -g < $merged_sectors >> $seqres.full
216                 echo fiemap_after >> $seqres.full
217                 sort -g < $fiemap_after >> $seqres.full
218                 exit
219         fi
220 done < $fiemap_after
221 echo "done."
222
223 status=0
224 exit