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
|
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2021 Oracle. All Rights Reserved.
#
# FS QA Test No. 176
#
# Ensure that online shrink does not let us shrink the fs such that the end
# of the filesystem is now in the middle of a sparse inode cluster.
#
. ./common/preamble
_begin_fstest auto quick shrinkfs prealloc punch
# Import common functions.
. ./common/filter
# real QA test starts here
# Modify as appropriate.
_supported_fs generic
_require_scratch
_require_xfs_sparse_inodes
_require_scratch_xfs_shrink
_require_xfs_io_command "falloc"
_require_xfs_io_command "fpunch"
_scratch_mkfs "-d size=50m -m crc=1 -i sparse" |
_filter_mkfs > /dev/null 2> $tmp.mkfs
. $tmp.mkfs # for isize
cat $tmp.mkfs >> $seqres.full
daddr_to_fsblocks=$((dbsize / 512))
convert_units() {
_scratch_xfs_db -f -c "$@" | sed -e 's/^.*(\([0-9]*\)).*$/\1/g'
}
# Figure out the next possible inode number after the log, since we can't
# shrink or relocate the log
logstart=$(_scratch_xfs_get_metadata_field 'logstart' 'sb')
if [ $logstart -gt 0 ]; then
logblocks=$(_scratch_xfs_get_metadata_field 'logblocks' 'sb')
logend=$((logstart + logblocks))
logend_agno=$(convert_units "convert fsb $logend agno")
logend_agino=$(convert_units "convert fsb $logend agino")
else
logend_agno=0
logend_agino=0
fi
_scratch_mount
_xfs_force_bdev data $SCRATCH_MNT
old_dblocks=$($XFS_IO_PROG -c 'statfs' $SCRATCH_MNT | grep geom.datablocks)
mkdir $SCRATCH_MNT/save/ $SCRATCH_MNT/urk/
sino=$(stat -c '%i' $SCRATCH_MNT/save)
_consume_freesp()
{
file=$1
# consume nearly all available space (leave ~1MB)
avail=`_get_available_space $SCRATCH_MNT`
filesizemb=$((avail / 1024 / 1024 - 1))
$XFS_IO_PROG -fc "falloc 0 ${filesizemb}m" $file
}
# Allocate inodes in a directory until failure.
_alloc_inodes()
{
dir=$1
i=0
while [ true ]; do
touch $dir/$i 2>> $seqres.full || break
i=$((i + 1))
done
}
# Find a sparse inode cluster after logend_agno/logend_agino.
find_sparse_clusters()
{
for ((agno = agcount - 1; agno >= logend_agno; agno--)); do
_scratch_xfs_db -c "agi $agno" -c "addr root" -c "btdump" | \
tr ':[,]' ' ' | \
awk -v "agno=$agno" \
-v "agino=$logend_agino" \
'{if ($2 >= agino && and(strtonum($3), 0x8000)) {printf("%s %s %s\n", agno, $2, $3);}}' | \
tac
done
}
# Calculate the fs inode chunk size based on the inode size and fixed 64-inode
# record. This value is used as the target level of free space fragmentation
# induced by the test (i.e., max size of free extents). We don't need to go
# smaller than a full chunk because the XFS block allocator tacks on alignment
# requirements to the size of the requested allocation. In other words, a chunk
# sized free chunk is not enough to guarantee a successful chunk sized
# allocation.
XFS_INODES_PER_CHUNK=64
CHUNK_SIZE=$((isize * XFS_INODES_PER_CHUNK))
_consume_freesp $SCRATCH_MNT/spc
# Now that the fs is nearly full, punch holes in every other $CHUNK_SIZE range
# of the space consumer file. The goal here is to end up with a sparse cluster
# at the end of the fs (and past any internal log), where the chunks at the end
# of the cluster are sparse.
offset=`_get_filesize $SCRATCH_MNT/spc`
offset=$((offset - $CHUNK_SIZE * 2))
nr=0
while [ $offset -ge 0 ]; do
$XFS_IO_PROG -c "fpunch $offset $CHUNK_SIZE" $SCRATCH_MNT/spc \
2>> $seqres.full || _fail "fpunch failed"
# allocate as many inodes as possible
mkdir -p $SCRATCH_MNT/urk/offset.$offset > /dev/null 2>&1
_alloc_inodes $SCRATCH_MNT/urk/offset.$offset
offset=$((offset - $CHUNK_SIZE * 2))
# Every five times through the loop, see if we got a sparse cluster
nr=$((nr + 1))
if [ $((nr % 5)) -eq 4 ]; then
_scratch_unmount
find_sparse_clusters > $tmp.clusters
if [ -s $tmp.clusters ]; then
break;
fi
_scratch_mount
fi
done
test -s $tmp.clusters || _notrun "Could not create a sparse inode cluster"
echo clusters >> $seqres.full
cat $tmp.clusters >> $seqres.full
# Figure out which inode numbers are in that last cluster. We need to preserve
# that cluster but delete everything else ahead of shrinking.
icluster_agno=$(head -n 1 $tmp.clusters | cut -d ' ' -f 1)
icluster_agino=$(head -n 1 $tmp.clusters | cut -d ' ' -f 2)
icluster_ino=$(convert_units "convert agno $icluster_agno agino $icluster_agino ino")
# Check that the save directory isn't going to prevent us from shrinking
test $sino -lt $icluster_ino || \
echo "/save inode comes after target cluster, test may fail"
# Save the inodes in the last cluster and delete everything else
_scratch_mount
rm -r $SCRATCH_MNT/spc
for ((ino = icluster_ino; ino < icluster_ino + XFS_INODES_PER_CHUNK; ino++)); do
find $SCRATCH_MNT/urk/ -inum "$ino" -print0 | xargs -r -0 mv -t $SCRATCH_MNT/save/
done
rm -rf $SCRATCH_MNT/urk/ $SCRATCH_MNT/save/*/*
sync
$XFS_IO_PROG -c 'fsmap -vvvvv' $SCRATCH_MNT &>> $seqres.full
# Propose shrinking the filesystem such that the end of the fs ends up in the
# sparse part of our sparse cluster. Remember, the last block of that cluster
# ought to be free.
target_ino=$((icluster_ino + XFS_INODES_PER_CHUNK - 1))
for ((ino = target_ino; ino >= icluster_ino; ino--)); do
found=$(find $SCRATCH_MNT/save/ -inum "$ino" | wc -l)
test $found -gt 0 && break
ino_daddr=$(convert_units "convert ino $ino daddr")
new_size=$((ino_daddr / daddr_to_fsblocks))
echo "Hope to fail at shrinking to $new_size" >> $seqres.full
$XFS_GROWFS_PROG -D $new_size $SCRATCH_MNT &>> $seqres.full
res=$?
# Make sure shrink did not work
new_dblocks=$($XFS_IO_PROG -c 'statfs' $SCRATCH_MNT | grep geom.datablocks)
if [ "$new_dblocks" != "$old_dblocks" ]; then
echo "should not have shrank $old_dblocks -> $new_dblocks"
break
fi
if [ $res -eq 0 ]; then
echo "shrink to $new_size (ino $ino) should have failed"
break
fi
done
# success, all done
echo Silence is golden
status=0
exit
|