blob: ba4aae5910c49cf3b92e1de1a1a238947a95a07d [file] [log] [blame]
#! /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
# 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/
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