--- /dev/null
+#!/bin/bash
+# FS QA Test No. xfs/014
+#
+# Test the behavior of XFS dynamic speculative preallocation at ENOSPC and
+# EDQUOT conditions. Speculative preallocation allocates post-EOF space to files
+# as they are extended. This test creates conditions where an fs is near a space
+# limit with lingering, relatively significant preallocations and verifies that
+# new writers reclaim said preallocations rather than prematurely fail with
+# ENOSPC/EDQUOT.
+#
+#-----------------------------------------------------------------------
+# Copyright (c) 2014 Red Hat, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#-----------------------------------------------------------------------
+#
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/quota
+
+_cleanup()
+{
+ cd /
+ umount $LOOP_MNT 2>/dev/null
+ umount $SCRATCH_MNT 2>/dev/null
+ rm -f $tmp.*
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# Create a file using a repeated open, extending write and close pattern. This
+# causes the preallocation to persist after the file is closed. Preallocation
+# will not be reclaimed unless the inode is evicted or we hit an allocation
+# failure.
+_spec_prealloc_file()
+{
+ file=$1
+
+ rm -f $file
+
+ # a few file extending open-write-close cycles should be enough to
+ # trigger the fs to retain preallocation. write 256k in 32k intervals to
+ # be sure
+ for i in $(seq 0 32768 262144); do
+ $XFS_IO_PROG -f -c "pwrite $i 32k" $file >> $seqres.full
+ done
+
+ # write a 4k aligned amount of data to keep the calculations simple
+ $XFS_IO_PROG -c "pwrite 0 128m" $file >> $seqres.full
+
+ size=`stat -c "%s" $file`
+ blocks=`stat -c "%b" $file`
+ blocksize=`stat -c "%B" $file`
+
+ prealloc_size=$((blocks * blocksize - size))
+ if [ $prealloc_size -eq 0 ]; then
+ echo "Warning: No speculative preallocation for $file." \
+ "Check use of the allocsize= mount option."
+ fi
+
+ # keep a running total of how much preallocation we've created
+ TOTAL_PREALLOC=$((TOTAL_PREALLOC + prealloc_size))
+}
+
+_consume_free_space()
+{
+ dir=$1
+
+ # allocate all but 10MB of available space
+ freesp=`df -m $dir | awk '/^\// { print $4 - 10 }'`
+ $XFS_IO_PROG -f -c "falloc 0 ${freesp}M" $dir/spc
+}
+
+# Create several files with preallocation and consume the remaining free space
+# via fallocate to the put the fs at ENOSPC. Create a set of background writers
+# to write into ENOSPC and cause the preallocation to be reclaimed and
+# reallocated to the new writers.
+_test_enospc()
+{
+ dir=$1
+
+ rm -rf $dir/*
+
+ TOTAL_PREALLOC=0
+ for i in $(seq 0 3); do
+ _spec_prealloc_file $dir/pre$i
+ done
+
+ _consume_free_space $dir
+
+ # consume 1/2 of the current preallocation across the set of 4 writers
+ write_size=$((TOTAL_PREALLOC / 2 / 4))
+ for i in $(seq 0 3); do
+ $XFS_IO_PROG -f -c "pwrite 0 $write_size" $dir/file.$i \
+ >> $seqres.full &
+ done
+
+ wait
+}
+
+# Create preallocations accounted by both user and group quotas. Set the
+# associated quota hard limits to put them at EDQUOT. Verify that a new writer
+# reclaims the preallocated space and proceeds without error.
+_test_edquot()
+{
+ dir=$1
+
+ rm -rf $dir/*
+
+ TOTAL_PREALLOC=0
+ _spec_prealloc_file $dir/user
+ chown $qa_user $dir/user
+
+ _spec_prealloc_file $dir/group
+ chgrp $qa_group $dir/group
+
+ # writing to a file under both quotas means both will be reclaimed on
+ # allocation failure
+ touch $dir/file
+ chown $qa_user $dir/file
+ chgrp $qa_group $dir/file
+
+ # put both quotas at EDQUOT
+ blks=`$XFS_QUOTA_PROG -xc "quota -u $qa_user" $dir | \
+ tail -n 1 | awk '{ print $2 }'`
+ $XFS_QUOTA_PROG -xc "limit -u bhard=${blks}k $qa_user" $dir
+ blks=`$XFS_QUOTA_PROG -xc "quota -g $qa_grup" $dir | \
+ tail -n 1 | awk '{ print $2 }'`
+ $XFS_QUOTA_PROG -xc "limit -g bhard=${blks}k $qa_group" $dir
+
+ # each quota has a single file worth of preallocation to reclaim. leave
+ # some wiggle room and write to 1/3 the total.
+ write_size=$((TOTAL_PREALLOC / 3))
+ $XFS_IO_PROG -c "pwrite 0 $write_size" $dir/file >> $seqres.full
+}
+
+# real QA test starts here
+_supported_fs xfs
+_supported_os Linux
+
+_require_scratch
+_require_xfs_io_command "falloc"
+_require_loop
+_require_quota
+_require_user
+_require_group
+
+rm -f $seqres.full
+
+echo "Silence is golden."
+
+_scratch_mkfs_xfs >> $seqres.full 2>&1
+_scratch_mount
+
+# make sure the background eofblocks scanner doesn't interfere
+orig_sp_time=`cat /proc/sys/fs/xfs/speculative_prealloc_lifetime`
+echo 9999 > /proc/sys/fs/xfs/speculative_prealloc_lifetime
+
+LOOP_FILE=$SCRATCH_MNT/$seq.fs
+LOOP_MNT=$SCRATCH_MNT/$seq.mnt
+
+$MKFS_XFS_PROG -d "file=1,name=$LOOP_FILE,size=10g" >> $seqres.full 2>&1
+
+mkdir -p $LOOP_MNT
+mount -t xfs -o loop,uquota,gquota $LOOP_FILE $LOOP_MNT || \
+ _fail "Failed to mount loop fs."
+
+_test_enospc $LOOP_MNT
+_test_edquot $LOOP_MNT
+
+umount $LOOP_MNT
+
+echo $orig_sp_time > /proc/sys/fs/xfs/speculative_prealloc_lifetime
+
+umount $SCRATCH_MNT
+_check_scratch_fs
+
+status=0
+exit