new file mode 100755
@@ -0,0 +1,207 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Oracle. All Rights Reserved.
+#
+# FS QA Test 780
+#
+# Functional test for commit:
+#
+# f38a032b165d ("xfs: fix I_DONTCACHE")
+#
+# Functional testing for the I_DONTCACHE inode flag, as set by the BULKSTAT
+# ioctl. This flag neuters the inode cache's tendency to try to hang on to
+# incore inodes for a while after the last program closes the file, which
+# is helpful for filesystem scanners to avoid trashing the inode cache.
+#
+# However, the inode cache doesn't always honor the DONTCACHE behavior -- the
+# only time it really applies is to cache misses from a bulkstat scan. If
+# any other threads accessed the inode before or immediately after the scan,
+# the DONTCACHE flag is ignored. This includes other scans.
+#
+# Regrettably, there is no way to poke /only/ XFS inode reclamation directly,
+# so we're stuck with setting xfssyncd_centisecs to a low value and sleeping
+# while watching the internal inode cache counters.
+#
+. ./common/preamble
+_begin_fstest auto ioctl
+
+_cleanup()
+{
+ cd /
+ rm -r -f $tmp.*
+ test -n "$old_centisecs" && echo "$old_centisecs" > "$xfs_centisecs_file"
+}
+
+# Import common functions.
+. ./common/filter
+
+# real QA test starts here
+
+# Modify as appropriate.
+_supported_fs generic
+_require_xfs_io_command "bulkstat"
+_require_scratch
+
+# We require /sys/fs/xfs/$device/stats/stats to monitor per-filesystem inode
+# cache usage.
+_require_fs_sysfs stats/stats
+
+count_xfs_inode_objs() {
+ _get_fs_sysfs_attr $SCRATCH_DEV stats/stats | awk '/vnodes/ {print $2}'
+}
+
+dump_debug_info() {
+ echo "round $1 baseline: $baseline_count high: $high_count fresh: $fresh_count post: $post_count end: $end_count" >> $seqres.full
+}
+
+# Either of these need to be available to monitor slab usage
+xfs_ino_objcount_file=/sys/kernel/slab/xfs_inode/objects
+slabinfo_file=/proc/slabinfo
+if [ ! -r "$xfs_ino_objcount_file" ] && [ ! -r "$slabinfo_file" ]; then
+ _notrun "Cannot find xfs_inode slab count?"
+fi
+
+# Background reclamation of disused xfs inodes is scheduled for ($xfssyncd / 6)
+# centiseconds after the first inode is tagged for reclamation. It's not great
+# to encode this implementation detail in a test like this, but there isn't
+# any means to trigger *only* inode cache reclaim -- actual memory pressure
+# can trigger the VFS to drop non-DONTCACHE inodes, which is not what we want.
+xfs_centisecs_file=/proc/sys/fs/xfs/xfssyncd_centisecs
+test -w "$xfs_centisecs_file" || _notrun "Cannot find xfssyncd_centisecs?"
+
+# Set the syncd knob to the minimum value 100cs (aka 1s) so that we don't have
+# to wait forever.
+old_centisecs="$(cat "$xfs_centisecs_file")"
+echo 100 > "$xfs_centisecs_file" || _notrun "Cannot adjust xfssyncd_centisecs?"
+delay_centisecs="$(cat "$xfs_centisecs_file")"
+
+# Sleep one second more than the xfssyncd delay to give background inode
+# reclaim enough time to run.
+sleep_seconds=$(( ( (99 + (delay_centisecs / 6) ) / 100) + 1))
+echo "Will sleep $sleep_seconds seconds to expire inodes" >> $seqres.full
+
+_scratch_mkfs >> $seqres.full
+_scratch_mount >> $seqres.full
+
+junkdir=$SCRATCH_MNT/$seq.junk
+
+# Sample the baseline count of cached inodes after a fresh remount.
+_scratch_cycle_mount
+baseline_count=$(count_xfs_inode_objs)
+
+# Create a junk directory with about a thousand files.
+nr_files=1024
+mkdir -p $junkdir
+for ((i = 0; i < nr_files; i++)); do
+ touch "$junkdir/$i"
+done
+new_files=$(find $junkdir | wc -l)
+echo "created $new_files files" >> $seqres.full
+_within_tolerance "new file count" $new_files $nr_files 5 -v
+
+# Sanity check: Make sure that all those new inodes are still in the cache.
+# We assume that memory limits are not so low that reclaim started for a bunch
+# of empty files. We hope there will never be more than 100 metadata inodes
+# or automatic mount time scanners.
+high_count=$(count_xfs_inode_objs)
+echo "cached inodes: $high_count" >> $seqres.full
+_within_tolerance "inodes after creating files" $high_count $new_files 0 100 -v
+
+################
+# Round 1: Check DONTCACHE behavior when it is invoked once. The inodes should
+# be reclaimed if we wait longer than the reclaim interval.
+echo "Round 1"
+
+_scratch_cycle_mount
+fresh_count=$(count_xfs_inode_objs)
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+post_count=$(count_xfs_inode_objs)
+sleep $sleep_seconds
+end_count=$(count_xfs_inode_objs)
+dump_debug_info 1
+
+# Even with our greatly reduced reclaim timeout, the inodes should still be in
+# memory immediately after the bulkstat concludes.
+_within_tolerance "inodes after bulkstat" $post_count $high_count 5 -v
+
+# After we've given inode reclaim time to run, the inodes should no longer be
+# cached in memory, which means we should have the fresh count again.
+_within_tolerance "inodes after expire" $end_count $fresh_count 5 -v
+
+################
+# Round 2: Check DONTCACHE behavior when it is invoked multiple times in rapid
+# succession. The inodes should remain in memory even after reclaim because
+# the cache notices repeat DONTCACHE hits and ignores them.
+echo "Round 2"
+
+_scratch_cycle_mount
+fresh_count=$(count_xfs_inode_objs)
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+post_count=$(count_xfs_inode_objs)
+sleep $sleep_seconds
+end_count=$(count_xfs_inode_objs)
+dump_debug_info 2
+
+# Inodes should still be in memory immediately after the second bulkstat
+# concludes and after the reclaim interval.
+_within_tolerance "inodes after double bulkstat" $post_count $high_count 5 -v
+_within_tolerance "inodes after expire" $end_count $high_count 5 -v
+
+################
+# Round 3: Check DONTCACHE evictions when it is invoked repeatedly but with
+# enough time between scans for inode reclaim to remove the inodes.
+echo "Round 3"
+
+_scratch_cycle_mount
+fresh_count=$(count_xfs_inode_objs)
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+sleep $sleep_seconds
+post_count=$(count_xfs_inode_objs)
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+sleep $sleep_seconds
+end_count=$(count_xfs_inode_objs)
+dump_debug_info 3
+
+# Inodes should not still be cached after either scan and reclaim interval.
+_within_tolerance "inodes after slow bulkstat 1" $post_count $fresh_count 5 -v
+_within_tolerance "inodes after slow bulkstat 2" $end_count $fresh_count 5 -v
+
+################
+# Round 4: Check that DONTCACHE has no effect when all the files are read
+# immediately after a bulkstat.
+echo "Round 4"
+
+_scratch_cycle_mount
+fresh_count=$(count_xfs_inode_objs)
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+find $junkdir -type f -print0 | xargs -0 cat > /dev/null
+post_count=$(count_xfs_inode_objs)
+sleep $sleep_seconds
+end_count=$(count_xfs_inode_objs)
+dump_debug_info 4
+
+# Inodes should still be cached after the scan/read and the reclaim interval.
+_within_tolerance "inodes after bulkstat/read" $post_count $high_count 5 -v
+_within_tolerance "inodes after reclaim" $end_count $high_count 5 -v
+
+################
+# Round 5: Check that DONTCACHE has no effect if the inodes were already in
+# cache due to reader programs.
+echo "Round 5"
+
+_scratch_cycle_mount
+fresh_count=$(count_xfs_inode_objs)
+find $junkdir -type f -print0 | xargs -0 cat > /dev/null
+$XFS_IO_PROG -c 'bulkstat' $junkdir > /dev/null
+post_count=$(count_xfs_inode_objs)
+sleep $sleep_seconds
+end_count=$(count_xfs_inode_objs)
+dump_debug_info 5
+
+# Inodes should still be cached after the read/scan and the reclaim interval.
+_within_tolerance "inodes after read/bulkstat" $post_count $high_count 5 -v
+_within_tolerance "inodes after reclaim" $end_count $high_count 5 -v
+
+status=0
+exit
new file mode 100644
@@ -0,0 +1,18 @@
+QA output created by 780
+new file count is in range
+inodes after creating files is in range
+Round 1
+inodes after bulkstat is in range
+inodes after expire is in range
+Round 2
+inodes after double bulkstat is in range
+inodes after expire is in range
+Round 3
+inodes after slow bulkstat 1 is in range
+inodes after slow bulkstat 2 is in range
+Round 4
+inodes after bulkstat/read is in range
+inodes after reclaim is in range
+Round 5
+inodes after read/bulkstat is in range
+inodes after reclaim is in range