--- /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