1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2013 Red Hat, Inc., Tomas Racek <tracek@redhat.com>
#
# FS QA Test No. 746
#
# Test that filesystem sends discard requests only on free blocks
#
. ./common/preamble
_begin_fstest auto trim fiemap
_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=$(_small_fs_size_mb 300) # 200m phys/virt size
fi
[ "$FSTYP" = "ext4" ] && _require_dumpe2fs
[ "$FSTYP" = "btrfs" ] && _require_btrfs_command inspect-internal dump-super
[ "$FSTYP" = "btrfs" ] && _require_btrfs_command inspect-internal dump-tree
# Override the default cleanup function.
_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
# FIEMAP only works on regular files, so call it on the backing file
# and not the loop device like everything else
$XFS_IO_PROG -F -c fiemap $img_file | 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 $loop_dev 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" $loop_dev | $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 $loop_dev \
| sed -n 's/nodesize\s*\(.*\)/\1/p')
# Get holes within block groups
$BTRFS_UTIL_PROG inspect-internal dump-tree -t extent $loop_dev \
| $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 $loop_dev \
| $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
}
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 > $fiemap_ref
# Delete some files
find $loop_mnt -type f -print | $AWK_PROG \
'BEGIN {srand()}; {if(rand() > 0.7) printf("%s\0", $0);}' | xargs -0 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 > $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
|