common/xfs: refactor commands to select a particular xfs backing device
[xfstests-dev.git] / common / populate
index d166c24a75a05bef32459b3db69e54a2090b9d22..867776cdd189bf6184cc058692f1cd5d52e3ff47 100644 (file)
@@ -1,29 +1,18 @@
 ##/bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2015 Oracle.  All Rights Reserved.
+#
 # Routines for populating a scratch fs, and helpers to exercise an FS
 # once it's been fuzzed.
-#-----------------------------------------------------------------------
-#  Copyright (c) 2015 Oracle.  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; either version 2 of the License, or
-#  (at your option) any later version.
-#
-#  This program is distributed in the hope that it will 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 to the Free Software
-#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
-#  USA
-#
-#  Contact information: Silicon Graphics, Inc., 1500 Crittenden Lane,
-#  Mountain View, CA 94043, USA, or: http://www.sgi.com
-#-----------------------------------------------------------------------
 
-_require_xfs_io_command "falloc"
-_require_xfs_io_command "fpunch"
+. ./common/quota
+
+_require_populate_commands() {
+       _require_xfs_io_command "falloc"
+       _require_xfs_io_command "fpunch"
+       _require_test_program "punch-alternating"
+       _require_command "$XFS_DB_PROG" "xfs_db"
+}
 
 _require_xfs_db_blocktrash_z_command() {
        test "${FSTYP}" = "xfs" || _notrun "cannot run xfs_db on ${FSTYP}"
@@ -33,6 +22,26 @@ _require_xfs_db_blocktrash_z_command() {
 # Attempt to make files of "every" format for data, dirs, attrs etc.
 # (with apologies to Eric Sandeen for mutating xfser.sh)
 
+# Create a file of a given size.
+__populate_create_file() {
+       local sz="$1"
+       local fname="$2"
+
+       $XFS_IO_PROG -f -c "pwrite -S 0x62 -W -b 1m 0 $sz" "${fname}"
+}
+
+# Punch out every other hole in this file, if it exists.
+#
+# The goal here is to force the creation of a large number of metadata records
+# by creating a lot of tiny extent mappings in a file.  Callers should ensure
+# that fragmenting the file actually causes record creation.  Call this
+# function /after/ creating all other metadata structures.
+__populate_fragment_file() {
+       local fname="$1"
+
+       test -f "${fname}" && $here/src/punch-alternating "${fname}"
+}
+
 # Create a large directory
 __populate_create_dir() {
        name="$1"
@@ -69,46 +78,120 @@ __populate_create_attr() {
        done
 }
 
-# Fill up 60% of the remaining free space
+# Fill up some percentage of the remaining free space
 __populate_fill_fs() {
        dir="$1"
        pct="$2"
        test -z "${pct}" && pct=60
 
-       SRC_SZ="$(du -ks "${SRCDIR}" | cut -f 1)"
+       mkdir -p "${dir}/test/1"
+       cp -pRdu "${dir}"/S_IFREG* "${dir}/test/1/"
+
+       SRC_SZ="$(du -ks "${dir}/test/1" | cut -f 1)"
        FS_SZ="$(( $(stat -f "${dir}" -c '%a * %S') / 1024 ))"
 
        NR="$(( (FS_SZ * ${pct} / 100) / SRC_SZ ))"
-       test "${NR}" -lt 1 && NR=1
 
-       seq 1 "${NR}" | while read nr; do
-               cp -pRdu "${SRCDIR}" "${dir}/test.${nr}" >> $seqres.full 2>&1
+       echo "FILL FS"
+       echo "src_sz $SRC_SZ fs_sz $FS_SZ nr $NR"
+       seq 2 "${NR}" | while read nr; do
+               cp -pRdu "${dir}/test/1" "${dir}/test/${nr}"
        done
 }
 
+# For XFS, force on all the quota options if quota is enabled
+# and the user didn't feed us noquota.
+_populate_xfs_qmount_option()
+{
+       # User explicitly told us not to quota
+       if echo "${MOUNT_OPTIONS}" | grep -q 'noquota'; then
+               return
+       fi
+
+       # Don't bother if we can't turn on quotas
+       if [ ! -f /proc/fs/xfs/xqmstat ]; then
+               # No quota support
+               return
+       elif [ "${USE_EXTERNAL}" = "yes" ] && [ ! -z "${SCRATCH_RTDEV}" ]; then
+               # Quotas not supported on rt filesystems
+               return
+       elif [ -z "${XFS_QUOTA_PROG}" ]; then
+               # xfs quota tools not installed
+               return
+       fi
+
+       # Turn on all the quotas
+       if $XFS_INFO_PROG "${TEST_DIR}" | grep -q 'crc=1'; then
+               # v5 filesystems can have group & project quotas
+               quota="usrquota,grpquota,prjquota"
+       else
+               # v4 filesystems cannot mix group & project quotas
+               quota="usrquota,grpquota"
+       fi
+
+       # Inject our quota mount options
+       if echo "${MOUNT_OPTIONS}" | grep -q "${quota}"; then
+               return
+       elif echo "${MOUNT_OPTIONS}" | egrep -q '(quota|noenforce)'; then
+               _qmount_option "${quota}"
+       else
+               export MOUNT_OPTIONS="$MOUNT_OPTIONS -o ${quota}"
+               echo "MOUNT_OPTIONS = $MOUNT_OPTIONS" >>$seqres.full
+       fi
+}
+
 # Populate an XFS on the scratch device with (we hope) all known
 # types of metadata block
 _scratch_xfs_populate() {
+       fill=1
+
+       for arg in $@; do
+               case "${arg}" in
+               "nofill")
+                       fill=0;;
+               esac
+       done
+
+       _populate_xfs_qmount_option
        _scratch_mount
+
+       # We cannot directly force the filesystem to create the metadata
+       # structures we want; we can only achieve this indirectly by carefully
+       # crafting files and a directory tree.  Therefore, we must have exact
+       # control over the layout and device selection of all files created.
+       # Clear the rtinherit flag on the root directory so that files are
+       # always created on the data volume regardless of MKFS_OPTIONS.  We can
+       # set the realtime flag when needed.
+       _xfs_force_bdev data $SCRATCH_MNT
+
        blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")"
-       dblksz="$(xfs_info "${SCRATCH_MNT}" | grep naming.*bsize | sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g')"
+       dblksz="$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep naming.*bsize | sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g')"
+       crc="$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep crc= | sed -e 's/^.*crc=//g' -e 's/\([0-9]*\).*$/\1/g')"
+       if [ $crc -eq 1 ]; then
+               leaf_hdr_size=64
+       else
+               leaf_hdr_size=16
+       fi
        leaf_lblk="$((32 * 1073741824 / blksz))"
        node_lblk="$((64 * 1073741824 / blksz))"
 
        # Data:
 
+       # Fill up the root inode chunk
+       echo "+ fill root ino chunk"
+       seq 1 64 | while read f; do
+               $XFS_IO_PROG -f -c "truncate 0" "${SCRATCH_MNT}/dummy${f}"
+       done
+
        # Regular files
        # - FMT_EXTENTS
        echo "+ extents file"
-       $XFS_IO_PROG -f -c "pwrite -S 0x61 0 ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS"
+       __populate_create_file $blksz "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS"
 
        # - FMT_BTREE
        echo "+ btree extents file"
        nr="$((blksz * 2 / 16))"
-       $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/S_IFREG.FMT_BTREE"
-       for i in $(seq 1 2 ${nr}); do
-               $XFS_IO_PROG -f -c "fpunch $((i * blksz)) ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_BTREE"
-       done
+       __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/S_IFREG.FMT_BTREE"
 
        # Directories
        # - INLINE
@@ -123,11 +206,16 @@ _scratch_xfs_populate() {
        echo "+ leaf dir"
        __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF" "$((dblksz / 12))"
 
+       # - LEAFN
+       echo "+ leafn dir"
+       __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_LEAFN" "$(( ((dblksz - leaf_hdr_size) / 8) - 3 ))"
+
        # - NODE
        echo "+ node dir"
        __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_NODE" "$((16 * dblksz / 40))" true
 
        # - BTREE
+       echo "+ btree dir"
        __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_BTREE" "$((128 * dblksz / 40))" true
 
        # Symlinks
@@ -141,9 +229,12 @@ _scratch_xfs_populate() {
 
        # Char & block
        echo "+ special"
-       mkdir devices
        mknod "${SCRATCH_MNT}/S_IFCHR" c 1 1
-       mknod "${SCRATCH_MNT}/S_IFBLK" c 1 1
+       mknod "${SCRATCH_MNT}/S_IFBLK" b 1 1
+       mknod "${SCRATCH_MNT}/S_IFIFO" p
+
+       # special file with an xattr
+       setfacl -P -m u:nobody:r ${SCRATCH_MNT}/S_IFCHR
 
        # Attribute formats
        # LOCAL
@@ -162,16 +253,28 @@ _scratch_xfs_populate() {
        echo "+ btree attr"
        __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_BTREE" "$((64 * blksz / 40))" true
 
+       # trusted namespace
+       touch ${SCRATCH_MNT}/ATTR.TRUSTED
+       setfattr -n trusted.moo -v urk ${SCRATCH_MNT}/ATTR.TRUSTED
+
+       # security namespace
+       touch ${SCRATCH_MNT}/ATTR.SECURITY
+       setfattr -n security.foo -v bar ${SCRATCH_MNT}/ATTR.SECURITY
+
+       # system namespace
+       touch ${SCRATCH_MNT}/ATTR.SYSTEM
+       setfacl -m u:root:r ${SCRATCH_MNT}/ATTR.SYSTEM
+
        # FMT_EXTENTS with a remote less-than-a-block value
        echo "+ attr extents with a remote less-than-a-block value"
        touch "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE3K"
-       $XFS_IO_PROG -f -c "pwrite -S 0x43 0 3k" "${SCRATCH_MNT}/attrvalfile" > /dev/null
+       $XFS_IO_PROG -f -c "pwrite -S 0x43 0 $((blksz - 300))" "${SCRATCH_MNT}/attrvalfile" > /dev/null
        attr -q -s user.remotebtreeattrname "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE3K" < "${SCRATCH_MNT}/attrvalfile"
 
        # FMT_EXTENTS with a remote block-size value
        echo "+ attr extents with a remote one-block value"
        touch "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE4K"
-       $XFS_IO_PROG -f -c "pwrite -S 0x44 0 4k" "${SCRATCH_MNT}/attrvalfile" > /dev/null
+       $XFS_IO_PROG -f -c "pwrite -S 0x44 0 ${blksz}" "${SCRATCH_MNT}/attrvalfile" > /dev/null
        attr -q -s user.remotebtreeattrname "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE4K" < "${SCRATCH_MNT}/attrvalfile"
        rm -rf "${SCRATCH_MNT}/attrvalfile"
 
@@ -181,10 +284,62 @@ _scratch_xfs_populate() {
        $XFS_IO_PROG -f -c 'fsync' "${SCRATCH_MNT}/unused"
        rm -rf "${SCRATCH_MNT}/unused"
 
+       # Free space btree
+       echo "+ freesp btree"
+       nr="$((blksz * 2 / 8))"
+       __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/BNOBT"
+
+       # Inode btree
+       echo "+ inobt btree"
+       local ino_per_rec=64
+       local rec_per_btblock=16
+       local nr="$(( 2 * (blksz / rec_per_btblock) * ino_per_rec ))"
+       local dir="${SCRATCH_MNT}/INOBT"
+       mkdir -p "${dir}"
+       seq 0 "${nr}" | while read f; do
+               touch "${dir}/${f}"
+       done
+
+       seq 0 2 "${nr}" | while read f; do
+               rm -f "${dir}/${f}"
+       done
+
+       # Reverse-mapping btree
+       is_rmapbt="$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep -c 'rmapbt=1')"
+       if [ $is_rmapbt -gt 0 ]; then
+               echo "+ rmapbt btree"
+               nr="$((blksz * 2 / 24))"
+               __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/RMAPBT"
+       fi
+
+       # Realtime Reverse-mapping btree
+       is_rt="$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep -c 'rtextents=[1-9]')"
+       if [ $is_rmapbt -gt 0 ] && [ $is_rt -gt 0 ]; then
+               echo "+ rtrmapbt btree"
+               nr="$((blksz * 2 / 32))"
+               $XFS_IO_PROG -R -f -c 'truncate 0' "${SCRATCH_MNT}/RTRMAPBT"
+               __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/RTRMAPBT"
+       fi
+
+       # Reference-count btree
+       is_reflink="$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep -c 'reflink=1')"
+       if [ $is_reflink -gt 0 ]; then
+               echo "+ reflink btree"
+               nr="$((blksz * 2 / 12))"
+               __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/REFCOUNTBT"
+               cp --reflink=always "${SCRATCH_MNT}/REFCOUNTBT" "${SCRATCH_MNT}/REFCOUNTBT2"
+       fi
+
        # Copy some real files (xfs tests, I guess...)
        echo "+ real files"
-       #__populate_fill_fs "${SCRATCH_MNT}" 40
-       cp -pRdu --reflink=always "${SCRATCH_MNT}/S_IFREG.FMT_BTREE" "${SCRATCH_MNT}/S_IFREG.FMT_BTREE.REFLINK" 2> /dev/null
+       test $fill -ne 0 && __populate_fill_fs "${SCRATCH_MNT}" 5
+
+       # Make sure we get all the fragmentation we asked for
+       __populate_fragment_file "${SCRATCH_MNT}/S_IFREG.FMT_BTREE"
+       __populate_fragment_file "${SCRATCH_MNT}/BNOBT"
+       __populate_fragment_file "${SCRATCH_MNT}/RMAPBT"
+       __populate_fragment_file "${SCRATCH_MNT}/RTRMAPBT"
+       __populate_fragment_file "${SCRATCH_MNT}/REFCOUNTBT"
 
        umount "${SCRATCH_MNT}"
 }
@@ -192,6 +347,15 @@ _scratch_xfs_populate() {
 # Populate an ext4 on the scratch device with (we hope) all known
 # types of metadata block
 _scratch_ext4_populate() {
+       fill=1
+
+       for arg in $@; do
+               case "${arg}" in
+               "nofill")
+                       fill=0;;
+               esac
+       done
+
        _scratch_mount
        blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")"
        dblksz="${blksz}"
@@ -203,19 +367,16 @@ _scratch_ext4_populate() {
        # Regular files
        # - FMT_INLINE
        echo "+ inline file"
-       $XFS_IO_PROG -f -c "pwrite -S 0x61 0 1" "${SCRATCH_MNT}/S_IFREG.FMT_INLINE"
+       __populate_create_file 1 "${SCRATCH_MNT}/S_IFREG.FMT_INLINE"
 
        # - FMT_EXTENTS
        echo "+ extents file"
-       $XFS_IO_PROG -f -c "pwrite -S 0x61 0 ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS"
+       __populate_create_file $blksz "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS"
 
        # - FMT_ETREE
        echo "+ extent tree file"
        nr="$((blksz * 2 / 12))"
-       $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/S_IFREG.FMT_ETREE"
-       for i in $(seq 1 2 ${nr}); do
-               $XFS_IO_PROG -f -c "fpunch $((i * blksz)) ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_ETREE"
-       done
+       __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/S_IFREG.FMT_ETREE"
 
        # Directories
        # - INLINE
@@ -224,7 +385,7 @@ _scratch_ext4_populate() {
 
        # - BLOCK
        echo "+ block dir"
-       __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK" "$((dblksz / 24))"
+       __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK" "$((dblksz / 32))"
 
        # - HTREE
        echo "+ htree dir"
@@ -241,19 +402,34 @@ _scratch_ext4_populate() {
 
        # Char & block
        echo "+ special"
-       mkdir devices
        mknod "${SCRATCH_MNT}/S_IFCHR" c 1 1
-       mknod "${SCRATCH_MNT}/S_IFBLK" c 1 1
+       mknod "${SCRATCH_MNT}/S_IFBLK" b 1 1
+       mknod "${SCRATCH_MNT}/S_IFIFO" p
+
+       # special file with an xattr
+       setfacl -P -m u:nobody:r ${SCRATCH_MNT}/S_IFCHR
 
        # Attribute formats
        # LOCAL
        echo "+ local attr"
-       __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_LOCAL" 1
+       __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_LOCAL" 0
 
        # BLOCK
        echo "+ block attr"
        __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_BLOCK" "$((blksz / 40))"
 
+       # trusted namespace
+       touch ${SCRATCH_MNT}/ATTR.TRUSTED
+       setfattr -n trusted.moo -v urk ${SCRATCH_MNT}/ATTR.TRUSTED
+
+       # security namespace
+       touch ${SCRATCH_MNT}/ATTR.SECURITY
+       setfattr -n security.foo -v bar ${SCRATCH_MNT}/ATTR.SECURITY
+
+       # system namespace
+       touch ${SCRATCH_MNT}/ATTR.SYSTEM
+       setfacl -m u:root:r ${SCRATCH_MNT}/ATTR.SYSTEM
+
        # Make an unused inode
        echo "+ empty file"
        touch "${SCRATCH_MNT}/unused"
@@ -262,8 +438,10 @@ _scratch_ext4_populate() {
 
        # Copy some real files (xfs tests, I guess...)
        echo "+ real files"
-       __populate_fill_fs "${SCRATCH_MNT}"
-       cp -pRdu --reflink=always "${SCRATCH_MNT}/S_IFREG.FMT_ETREE" "${SCRATCH_MNT}/S_IREG.FMT_ETREE.REFLINK" 2> /dev/null
+       test $fill -ne 0 && __populate_fill_fs "${SCRATCH_MNT}" 5
+
+       # Make sure we get all the fragmentation we asked for
+       __populate_fragment_file "${SCRATCH_MNT}/S_IFREG.FMT_ETREE"
 
        umount "${SCRATCH_MNT}"
 }
@@ -277,38 +455,35 @@ __populate_find_inode() {
 
 # Check data fork format of XFS file
 __populate_check_xfs_dformat() {
-       dev="$1"
-       inode="$2"
-       format="$3"
+       inode="$1"
+       format="$2"
 
-       fmt="$($XFS_DB_PROG -c "inode ${inode}" -c 'p core.format' "${dev}" | sed -e 's/^.*(\([a-z]*\)).*$/\1/g')"
+       fmt="$(_scratch_xfs_db -c "inode ${inode}" -c 'p core.format' | sed -e 's/^.*(\([a-z]*\)).*$/\1/g')"
        test "${format}" = "${fmt}" || _fail "failed to create ino ${inode} dformat expected ${format} saw ${fmt}"
 }
 
 # Check attr fork format of XFS file
 __populate_check_xfs_aformat() {
-       dev="$1"
-       inode="$2"
-       format="$3"
+       inode="$1"
+       format="$2"
 
-       fmt="$($XFS_DB_PROG -c "inode ${inode}" -c 'p core.aformat' "${dev}" | sed -e 's/^.*(\([a-z]*\)).*$/\1/g')"
+       fmt="$(_scratch_xfs_db -c "inode ${inode}" -c 'p core.aformat' | sed -e 's/^.*(\([a-z]*\)).*$/\1/g')"
        test "${format}" = "${fmt}" || _fail "failed to create ino ${inode} aformat expected ${format} saw ${fmt}"
 }
 
 # Check structure of XFS directory
 __populate_check_xfs_dir() {
-       dev="$1"
-       inode="$2"
-       dtype="$3"
+       inode="$1"
+       dtype="$2"
 
        (test -n "${leaf_lblk}" && test -n "${node_lblk}") || _fail "must define leaf_lblk and node_lblk before calling __populate_check_xfs_dir"
        datab=0
        leafb=0
        freeb=0
-       #echo "== check dir ${inode} type ${dtype}" ; $XFS_DB_PROG -x -c "inode ${inode}" -c "bmap" "${SCRATCH_DEV}"
-       $XFS_DB_PROG -x -c "inode ${inode}" -c "dblock 0" -c "stack" "${SCRATCH_DEV}" | grep -q 'file data block is unmapped' || datab=1
-       $XFS_DB_PROG -x -c "inode ${inode}" -c "dblock ${leaf_lblk}" -c "stack" "${SCRATCH_DEV}" | grep -q 'file data block is unmapped' || leafb=1
-       $XFS_DB_PROG -x -c "inode ${inode}" -c "dblock ${node_lblk}" -c "stack" "${SCRATCH_DEV}" | grep -q 'file data block is unmapped' || freeb=1
+       #echo "== check dir ${inode} type ${dtype}" ; _scratch_xfs_db -x -c "inode ${inode}" -c "bmap"
+       _scratch_xfs_db -x -c "inode ${inode}" -c "dblock 0" -c "stack" | grep -q 'file data block is unmapped' || datab=1
+       _scratch_xfs_db -x -c "inode ${inode}" -c "dblock ${leaf_lblk}" -c "stack" | grep -q 'file data block is unmapped' || leafb=1
+       _scratch_xfs_db -x -c "inode ${inode}" -c "dblock ${node_lblk}" -c "stack" | grep -q 'file data block is unmapped' || freeb=1
 
        case "${dtype}" in
        "shortform"|"inline"|"local")
@@ -320,6 +495,11 @@ __populate_check_xfs_dir() {
        "leaf")
                (test "${datab}" -eq 1 && test "${leafb}" -eq 1 && test "${freeb}" -eq 0) || _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}"
                ;;
+       "leafn")
+               _scratch_xfs_db -x -c "inode ${inode}" -c "dblock ${leaf_lblk}" -c "p lhdr.info.hdr.magic" | grep -q '0x3dff' && return
+               _scratch_xfs_db -x -c "inode ${inode}" -c "dblock ${leaf_lblk}" -c "p lhdr.info.magic" | grep -q '0xd2ff' && return
+               _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}"
+               ;;
        "node"|"btree")
                (test "${datab}" -eq 1 && test "${leafb}" -eq 1 && test "${freeb}" -eq 1) || _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}"
                ;;
@@ -330,15 +510,14 @@ __populate_check_xfs_dir() {
 
 # Check structure of XFS attr
 __populate_check_xfs_attr() {
-       dev="$1"
-       inode="$2"
-       atype="$3"
+       inode="$1"
+       atype="$2"
 
        datab=0
        leafb=0
-       #echo "== check attr ${inode} type ${dtype}" ; $XFS_DB_PROG -x -c "inode ${inode}" -c "bmap -a" "${SCRATCH_DEV}"
-       $XFS_DB_PROG -x -c "inode ${inode}" -c "ablock 0" -c "stack" "${SCRATCH_DEV}" | grep -q 'file attr block is unmapped' || datab=1
-       $XFS_DB_PROG -x -c "inode ${inode}" -c "ablock 1" -c "stack" "${SCRATCH_DEV}" | grep -q 'file attr block is unmapped' || leafb=1
+       #echo "== check attr ${inode} type ${dtype}" ; _scratch_xfs_db -x -c "inode ${inode}" -c "bmap -a"
+       _scratch_xfs_db -x -c "inode ${inode}" -c "ablock 0" -c "stack" | grep -q 'file attr block is unmapped' || datab=1
+       _scratch_xfs_db -x -c "inode ${inode}" -c "ablock 1" -c "stack" | grep -q 'file attr block is unmapped' || leafb=1
 
        case "${atype}" in
        "shortform"|"inline"|"local")
@@ -355,6 +534,39 @@ __populate_check_xfs_attr() {
        esac
 }
 
+# Check that there's at least one per-AG btree with multiple levels
+__populate_check_xfs_agbtree_height() {
+       bt_type="$1"
+       nr_ags=$(_scratch_xfs_db -c 'sb 0' -c 'p agcount' | awk '{print $3}')
+
+       case "${bt_type}" in
+       "bno"|"cnt"|"rmap"|"refcnt")
+               hdr="agf"
+               bt_prefix="${bt_type}"
+               ;;
+       "ino")
+               hdr="agi"
+               bt_prefix=""
+               ;;
+       "fino")
+               hdr="agi"
+               bt_prefix="free_"
+               ;;
+       *)
+               _fail "Don't know about AG btree ${bt_type}"
+               ;;
+       esac
+
+       seq 0 $((nr_ags - 1)) | while read ag; do
+               bt_level=$(_scratch_xfs_db -c "${hdr} ${ag}" -c "p ${bt_prefix}level" | awk '{print $3}')
+               if [ "${bt_level}" -gt 1 ]; then
+                       return 100
+               fi
+       done
+       test $? -eq 100 || _fail "Failed to create ${bt_type} of sufficient height!"
+       return 1
+}
+
 # Check that populate created all the types of files we wanted
 _scratch_xfs_populate_check() {
        _scratch_mount
@@ -363,45 +575,58 @@ _scratch_xfs_populate_check() {
        inline_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_INLINE")"
        block_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK")"
        leaf_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF")"
+       leafn_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_LEAFN")"
        node_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_NODE")"
        btree_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_BTREE")"
        local_slink="$(__populate_find_inode "${SCRATCH_MNT}/S_IFLNK.FMT_LOCAL")"
        extents_slink="$(__populate_find_inode "${SCRATCH_MNT}/S_IFLNK.FMT_EXTENTS")"
        bdev="$(__populate_find_inode "${SCRATCH_MNT}/S_IFBLK")"
        cdev="$(__populate_find_inode "${SCRATCH_MNT}/S_IFCHR")"
+       fifo="$(__populate_find_inode "${SCRATCH_MNT}/S_IFIFO")"
        local_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_LOCAL")"
        leaf_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_LEAF")"
        node_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_NODE")"
        btree_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_BTREE")"
+       is_finobt=$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep -c 'finobt=1')
+       is_rmapbt=$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep -c 'rmapbt=1')
+       is_reflink=$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep -c 'reflink=1')
 
        blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")"
-       dblksz="$(xfs_info "${SCRATCH_MNT}" | grep naming.*bsize | sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g')"
+       dblksz="$($XFS_INFO_PROG "${SCRATCH_MNT}" | grep naming.*bsize | sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g')"
        leaf_lblk="$((32 * 1073741824 / blksz))"
        node_lblk="$((64 * 1073741824 / blksz))"
        umount "${SCRATCH_MNT}"
 
-       __populate_check_xfs_dformat "${SCRATCH_DEV}" "${extents_file}" "extents"
-       __populate_check_xfs_dformat "${SCRATCH_DEV}" "${btree_file}" "btree"
-       __populate_check_xfs_dir "${SCRATCH_DEV}" "${inline_dir}" "inline"
-       __populate_check_xfs_dir "${SCRATCH_DEV}" "${block_dir}" "block"
-       __populate_check_xfs_dir "${SCRATCH_DEV}" "${leaf_dir}" "leaf"
-       __populate_check_xfs_dir "${SCRATCH_DEV}" "${node_dir}" "node"
-       __populate_check_xfs_dir "${SCRATCH_DEV}" "${btree_dir}" "btree"
-       __populate_check_xfs_dformat "${SCRATCH_DEV}" "${btree_dir}" "btree"
-       __populate_check_xfs_dformat "${SCRATCH_DEV}" "${bdev}" "dev"
-       __populate_check_xfs_dformat "${SCRATCH_DEV}" "${cdev}" "dev"
-       __populate_check_xfs_attr "${SCRATCH_DEV}" "${local_attr}" "local"
-       __populate_check_xfs_attr "${SCRATCH_DEV}" "${leaf_attr}" "leaf"
-       __populate_check_xfs_attr "${SCRATCH_DEV}" "${node_attr}" "node"
-       __populate_check_xfs_attr "${SCRATCH_DEV}" "${btree_attr}" "btree"
-       __populate_check_xfs_aformat "${SCRATCH_DEV}" "${btree_attr}" "btree"
+       __populate_check_xfs_dformat "${extents_file}" "extents"
+       __populate_check_xfs_dformat "${btree_file}" "btree"
+       __populate_check_xfs_dir "${inline_dir}" "inline"
+       __populate_check_xfs_dir "${block_dir}" "block"
+       __populate_check_xfs_dir "${leaf_dir}" "leaf"
+       __populate_check_xfs_dir "${leafn_dir}" "leafn"
+       __populate_check_xfs_dir "${node_dir}" "node"
+       __populate_check_xfs_dir "${btree_dir}" "btree"
+       __populate_check_xfs_dformat "${btree_dir}" "btree"
+       __populate_check_xfs_dformat "${bdev}" "dev"
+       __populate_check_xfs_dformat "${cdev}" "dev"
+       __populate_check_xfs_dformat "${fifo}" "dev"
+       __populate_check_xfs_attr "${local_attr}" "local"
+       __populate_check_xfs_attr "${leaf_attr}" "leaf"
+       __populate_check_xfs_attr "${node_attr}" "node"
+       __populate_check_xfs_attr "${btree_attr}" "btree"
+       __populate_check_xfs_aformat "${btree_attr}" "btree"
+       __populate_check_xfs_agbtree_height "bno"
+       __populate_check_xfs_agbtree_height "cnt"
+       __populate_check_xfs_agbtree_height "ino"
+       test $is_finobt -ne 0 && __populate_check_xfs_agbtree_height "fino"
+       test $is_rmapbt -ne 0 && __populate_check_xfs_agbtree_height "rmap"
+       test $is_reflink -ne 0 && __populate_check_xfs_agbtree_height "refcnt"
 }
 
 # Check data fork format of ext4 file
 __populate_check_ext4_dformat() {
-       dev="$1"
-       inode="$2"
-       format="$3"
+       dev="${SCRATCH_DEV}"
+       inode="$1"
+       format="$2"
 
        extents=0
        etree=0
@@ -426,9 +651,9 @@ __populate_check_ext4_dformat() {
 
 # Check attr fork format of ext4 file
 __populate_check_ext4_aformat() {
-       dev="$1"
-       inode="$2"
-       format="$3"
+       dev="${SCRATCH_DEV}"
+       inode="$1"
+       format="$2"
 
        ablock=1
        debugfs -R "stat <${inode}>" "${dev}" 2> /dev/null | grep 'File ACL: 0' -q && ablock=0
@@ -447,9 +672,9 @@ __populate_check_ext4_aformat() {
 
 # Check structure of ext4 dir
 __populate_check_ext4_dir() {
-       dev="$1"
-       inode="$2"
-       dtype="$3"
+       dev="${SCRATCH_DEV}"
+       inode="$1"
+       dtype="$2"
 
        htree=0
        inline=0
@@ -485,13 +710,13 @@ _scratch_ext4_populate_check() {
        block_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_BLOCK")"
        umount "${SCRATCH_MNT}"
 
-       __populate_check_ext4_dformat "${SCRATCH_DEV}" "${extents_file}" "extents"
-       __populate_check_ext4_dformat "${SCRATCH_DEV}" "${etree_file}" "etree"
-       __populate_check_ext4_dir "${SCRATCH_DEV}" "${block_dir}" "block"
-       __populate_check_ext4_dir "${SCRATCH_DEV}" "${htree_dir}" "htree"
-       __populate_check_ext4_dformat "${SCRATCH_DEV}" "${extents_slink}" "extents"
-       __populate_check_ext4_aformat "${SCRATCH_DEV}" "${local_attr}" "local"
-       __populate_check_ext4_aformat "${SCRATCH_DEV}" "${block_attr}" "block"
+       __populate_check_ext4_dformat "${extents_file}" "extents"
+       __populate_check_ext4_dformat "${etree_file}" "etree"
+       __populate_check_ext4_dir "${block_dir}" "block"
+       __populate_check_ext4_dir "${htree_dir}" "htree"
+       __populate_check_ext4_dformat "${extents_slink}" "extents"
+       __populate_check_ext4_aformat "${local_attr}" "local"
+       __populate_check_ext4_aformat "${block_attr}" "block"
 }
 
 # Populate a scratch FS and check the contents to make sure we got that
@@ -501,7 +726,7 @@ _scratch_populate() {
                _scratch_xfs_populate
                _scratch_xfs_populate_check
                ;;
-       "ext4")
+       "ext2"|"ext3"|"ext4")
                _scratch_ext4_populate
                _scratch_ext4_populate_check
                ;;
@@ -511,36 +736,176 @@ _scratch_populate() {
        esac
 }
 
-# Modify various files after a fuzzing operation
-_scratch_fuzz_modify() {
-       nr="$1"
-
-       test -z "${nr}" && nr=50000
-       echo "+++ touch ${nr} files"
-       $XFS_IO_PROG -f -c "pwrite -S 0x63 0 ${BLK_SZ}" "/tmp/afile" > /dev/null
-       date="$(date)"
-       find "${SCRATCH_MNT}/" -type f 2> /dev/null | head -n "${nr}" | while read f; do
-               setfattr -n "user.date" -v "${date}" "$f"
-               cat "/tmp/afile" >> "$f"
-               mv "$f" "$f.longer"
+# Fill a file system by repeatedly creating files in the given folder
+# starting with the given file size.  Files are reduced in size when
+# they can no longer fit until no more files can be created.
+_fill_fs()
+{
+       local file_size=$1
+       local dir=$2
+       local block_size=$3
+       local switch_user=$4
+       local file_count=1
+       local bytes_written=0
+       local use_falloc=1;
+
+       if [ $# -ne 4 ]; then
+               echo "Usage: _fill_fs filesize dir blocksize switch_user"
+               exit 1
+       fi
+
+       if [ $switch_user -eq 0 ]; then
+               mkdir -p $dir
+       else
+               _user_do "mkdir -p $dir"
+       fi
+       if [ ! -d $dir ]; then
+               return 0;
+       fi
+
+       testio=`$XFS_IO_PROG -F -fc "falloc 0 $block_size" $dir/$$.xfs_io 2>&1`
+       echo $testio | grep -q "not found" && use_falloc=0
+       echo $testio | grep -q "Operation not supported" && use_falloc=0
+
+       if [ $file_size -lt $block_size ]; then
+               $file_size = $block_size
+       fi
+
+       while [ $file_size -ge $block_size ]; do
+               bytes_written=0
+               if [ $switch_user -eq 0 ]; then
+                       if [ $use_falloc -eq 0 ]; then
+                               $XFS_IO_PROG -fc "pwrite -b 8388608 0 $file_size" \
+                                       $dir/$file_count
+                       else
+                               $XFS_IO_PROG -fc "falloc 0 $file_size" \
+                                       $dir/$file_count
+                       fi
+               else
+                       if [ $use_falloc -eq 0 ]; then
+                               _user_do "$XFS_IO_PROG -f -c \"pwrite -b 8388608 0 \
+                                       $file_size\" $dir/$file_count"
+                       else
+                               _user_do "$XFS_IO_PROG -f -c \"falloc 0 \
+                                       $file_size\" $dir/$file_count"
+                       fi
+               fi
+
+               if [ -f $dir/$file_count ]; then
+                       bytes_written=$(_get_filesize $dir/$file_count)
+               fi
+
+               # If there was no room to make the file, then divide it in
+               # half, and keep going
+               if [ $bytes_written -lt $file_size ]; then
+                       file_size=$((file_size / 2))
+               fi
+               file_count=$((file_count + 1))
        done
-       rm -rf "/tmp/afile"
+}
 
-       echo "+++ create files"
-       cp -pRdu "${SRCDIR}" "${SCRATCH_MNT}/test.moo"
-       sync
+# Compute the fs geometry description of a populated filesystem
+_scratch_populate_cache_tag() {
+       local extra_descr=""
+       local size="$(blockdev --getsz "${SCRATCH_DEV}")"
+       local logdev_sz="none"
+       local rtdev_sz="none"
 
-       echo "+++ remove files"
-       rm -rf "${SCRATCH_MNT}/test.moo"
-       rm -rf "${SCRATCH_MNT}/test.1"
+       if [ "${USE_EXTERNAL}" = "yes" ] && [ -n "${SCRATCH_LOGDEV}" ]; then
+               logdev_sz="$(blockdev --getsz "${SCRATCH_LOGDEV}")"
+       fi
+
+       if [ "${USE_EXTERNAL}" = "yes" ] && [ -n "${SCRATCH_RTDEV}" ]; then
+               rtdev_sz="$(blockdev --getsz "${SCRATCH_RTDEV}")"
+       fi
+
+       case "${FSTYP}" in
+       "ext4")
+               extra_descr="LOGDEV_SIZE ${logdev_sz}"
+               ;;
+       "xfs")
+               extra_descr="LOGDEV_SIZE ${logdev_sz} RTDEV_SIZE ${rtdev_sz}"
+               _populate_xfs_qmount_option
+               if echo "${MOUNT_OPTIONS}" | grep -q 'usrquota'; then
+                       extra_descr="${extra_descr} QUOTAS"
+               fi
+               ;;
+       esac
+       echo "FSTYP ${FSTYP} MKFS_OPTIONS ${MKFS_OPTIONS} SIZE ${size} ${extra_descr} ARGS $@"
 }
 
-# Try to access files after fuzzing
-_scratch_fuzz_test() {
-       echo "+++ ls -laR" >> $seqres.full
-       ls -laR "${SCRATCH_MNT}/test.1/" >/dev/null 2>&1
+# Restore a cached populated fs from a metadata dump
+_scratch_populate_restore_cached() {
+       local metadump="$1"
+
+       # If we're configured for compressed dumps and there isn't already an
+       # uncompressed dump, see if we can use DUMP_COMPRESSOR to decompress
+       # something.
+       if [ -n "$DUMP_COMPRESSOR" ]; then
+               for compr in "$metadump".*; do
+                       [ -e "$compr" ] && $DUMP_COMPRESSOR -d -f -k "$compr" && break
+               done
+       fi
 
-       echo "+++ cat files" >> $seqres.full
-       (find "${SCRATCH_MNT}/test.1/" -type f -size -1048576k -print0 | xargs -0 cat) >/dev/null 2>&1
+       test -r "$metadump" || return 1
+
+       case "${FSTYP}" in
+       "xfs")
+               xfs_mdrestore "${metadump}" "${SCRATCH_DEV}" && return 0
+               ;;
+       "ext2"|"ext3"|"ext4")
+               # ext4 cannot e2image external logs, so we cannot restore
+               test -n "${SCRATCH_LOGDEV}" && return 1
+               e2image -r "${metadump}" "${SCRATCH_DEV}" && return 0
+               ;;
+       esac
+       return 1
 }
 
+# Populate a scratch FS from scratch or from a cached image.
+_scratch_populate_cached() {
+       local meta_descr="$(_scratch_populate_cache_tag "$@")"
+       local meta_tag="$(echo "${meta_descr}" | md5sum - | cut -d ' ' -f 1)"
+       local metadump_stem="${TEST_DIR}/__populate.${FSTYP}.${meta_tag}"
+
+       # These variables are shared outside this function
+       POPULATE_METADUMP="${metadump_stem}.metadump"
+       POPULATE_METADUMP_DESCR="${metadump_stem}.txt"
+
+       # Don't keep metadata images cached for more 48 hours...
+       rm -rf "$(find "${POPULATE_METADUMP}" -mtime +2 2>/dev/null)"
+
+       # Throw away cached image if it doesn't match our spec.
+       cmp -s "${POPULATE_METADUMP_DESCR}" <(echo "${meta_descr}") || \
+               rm -rf "${POPULATE_METADUMP}"
+
+       # Try to restore from the metadump
+       _scratch_populate_restore_cached "${POPULATE_METADUMP}" && \
+               return
+
+       # Oh well, just create one from scratch
+       _scratch_mkfs
+       echo "${meta_descr}" > "${POPULATE_METADUMP_DESCR}"
+       case "${FSTYP}" in
+       "xfs")
+               _scratch_xfs_populate $@
+               _scratch_xfs_populate_check
+               _scratch_xfs_metadump "${POPULATE_METADUMP}"
+
+               local logdev=
+               [ "$USE_EXTERNAL" = yes -a ! -z "$SCRATCH_LOGDEV" ] && \
+                       logdev=$SCRATCH_LOGDEV
+
+               _xfs_metadump "$POPULATE_METADUMP" "$SCRATCH_DEV" "$logdev" \
+                       compress
+               ;;
+       "ext2"|"ext3"|"ext4")
+               _scratch_ext4_populate $@
+               _scratch_ext4_populate_check
+               _ext4_metadump "${SCRATCH_DEV}" "${POPULATE_METADUMP}" compress
+               ;;
+       *)
+               _fail "Don't know how to populate a ${FSTYP} filesystem."
+               ;;
+       esac
+}