populate: create block devices when pre-populating filesystems
[xfstests-dev.git] / common / populate
index b2dd053ff781e2a4e4d50b4db57c23e6a73b3183..8f42a528fca734b139b844005b2fcb5f234fc677 100644 (file)
@@ -1,26 +1,10 @@
 ##/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
-#-----------------------------------------------------------------------
+
 . ./common/quota
 
 _require_populate_commands() {
@@ -38,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"
@@ -150,8 +154,24 @@ _scratch_xfs_populate() {
 
        _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_IO_PROG -c 'chattr -t' $SCRATCH_MNT
+
        blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")"
        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))"
 
@@ -166,13 +186,12 @@ _scratch_xfs_populate() {
        # 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"
-       ./src/punch-alternating "${SCRATCH_MNT}/S_IFREG.FMT_BTREE"
+       __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/S_IFREG.FMT_BTREE"
 
        # Directories
        # - INLINE
@@ -187,6 +206,10 @@ _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
@@ -207,7 +230,7 @@ _scratch_xfs_populate() {
        # Char & block
        echo "+ special"
        mknod "${SCRATCH_MNT}/S_IFCHR" c 1 1
-       mknod "${SCRATCH_MNT}/S_IFBLK" c 1 1
+       mknod "${SCRATCH_MNT}/S_IFBLK" b 1 1
 
        # special file with an xattr
        setfacl -P -m u:nobody:r ${SCRATCH_MNT}/S_IFCHR
@@ -263,16 +286,29 @@ _scratch_xfs_populate() {
        # Free space btree
        echo "+ freesp btree"
        nr="$((blksz * 2 / 8))"
-       $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/BNOBT"
-       ./src/punch-alternating "${SCRATCH_MNT}/BNOBT"
+       __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))"
-               $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/RMAPBT"
-               ./src/punch-alternating "${SCRATCH_MNT}/RMAPBT"
+               __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/RMAPBT"
        fi
 
        # Realtime Reverse-mapping btree
@@ -280,8 +316,8 @@ _scratch_xfs_populate() {
        if [ $is_rmapbt -gt 0 ] && [ $is_rt -gt 0 ]; then
                echo "+ rtrmapbt btree"
                nr="$((blksz * 2 / 32))"
-               $XFS_IO_PROG -f -R -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/RTRMAPBT"
-               ./src/punch-alternating "${SCRATCH_MNT}/RTRMAPBT"
+               $XFS_IO_PROG -R -f -c 'truncate 0' "${SCRATCH_MNT}/RTRMAPBT"
+               __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/RTRMAPBT"
        fi
 
        # Reference-count btree
@@ -289,15 +325,21 @@ _scratch_xfs_populate() {
        if [ $is_reflink -gt 0 ]; then
                echo "+ reflink btree"
                nr="$((blksz * 2 / 12))"
-               $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/REFCOUNTBT"
+               __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/REFCOUNTBT"
                cp --reflink=always "${SCRATCH_MNT}/REFCOUNTBT" "${SCRATCH_MNT}/REFCOUNTBT2"
-               ./src/punch-alternating "${SCRATCH_MNT}/REFCOUNTBT"
        fi
 
        # Copy some real files (xfs tests, I guess...)
        echo "+ real files"
        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}"
 }
 
@@ -324,17 +366,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"
-       ./src/punch-alternating "${SCRATCH_MNT}/S_IFREG.FMT_ETREE"
+       __populate_create_file $((blksz * nr)) "${SCRATCH_MNT}/S_IFREG.FMT_ETREE"
 
        # Directories
        # - INLINE
@@ -361,7 +402,7 @@ _scratch_ext4_populate() {
        # Char & block
        echo "+ special"
        mknod "${SCRATCH_MNT}/S_IFCHR" c 1 1
-       mknod "${SCRATCH_MNT}/S_IFBLK" c 1 1
+       mknod "${SCRATCH_MNT}/S_IFBLK" b 1 1
 
        # special file with an xattr
        setfacl -P -m u:nobody:r ${SCRATCH_MNT}/S_IFCHR
@@ -397,6 +438,9 @@ _scratch_ext4_populate() {
        echo "+ real files"
        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}"
 }
 
@@ -449,6 +493,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}"
                ;;
@@ -524,6 +573,7 @@ _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")"
@@ -549,6 +599,7 @@ _scratch_xfs_populate_check() {
        __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"
@@ -561,6 +612,8 @@ _scratch_xfs_populate_check() {
        __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"
 }
@@ -735,7 +788,7 @@ _fill_fs()
                fi
 
                if [ -f $dir/$file_count ]; then
-                       bytes_written=$(stat -c '%s' $dir/$file_count)
+                       bytes_written=$(_get_filesize $dir/$file_count)
                fi
 
                # If there was no room to make the file, then divide it in
@@ -747,20 +800,14 @@ _fill_fs()
        done
 }
 
-# Populate a scratch FS from scratch or from a cached image.
-_scratch_populate_cached() {
-       POPULATE_METADUMP="${TEST_DIR}/__populate.${FSTYP}"
-       POPULATE_METADUMP_DESCR="${TEST_DIR}/__populate.${FSTYP}.txt"
+# Compute the fs geometry description of a populated filesystem
+_scratch_populate_cache_tag() {
+       local extra_descr=""
+       local size="$(blockdev --getsz "${SCRATCH_DEV}")"
 
-       # 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.
        case "${FSTYP}" in
        "ext4")
                extra_descr="LOGDEV ${SCRATCH_LOGDEV} USE_EXTERNAL ${USE_EXTERNAL}"
-               # ext4 cannot e2image external logs, so we cannot restore
-               test -n "${SCRATCH_LOGDEV}" && rm -f "${POPULATE_METADUMP}"
                ;;
        "xfs")
                extra_descr="LOGDEV ${SCRATCH_LOGDEV} USE_EXTERNAL ${USE_EXTERNAL} RTDEV ${SCRATCH_RTDEV}"
@@ -769,24 +816,59 @@ _scratch_populate_cached() {
                        extra_descr="${extra_descr} QUOTAS"
                fi
                ;;
-       *)
-               extra_descr="";;
        esac
-       meta_descr="FSTYP ${FSTYP} MKFS_OPTIONS ${MKFS_OPTIONS} SIZE $(blockdev --getsz "${SCRATCH_DEV}") ${extra_descr} ARGS $@"
-       cmp -s "${POPULATE_METADUMP_DESCR}" <(echo "${meta_descr}") || rm -rf "${POPULATE_METADUMP}"
-
-       # Do we have a cached image?
-       if [ -r "${POPULATE_METADUMP}" ]; then
-               case "${FSTYP}" in
-               "xfs")
-                       xfs_mdrestore "${POPULATE_METADUMP}" "${SCRATCH_DEV}" && return
-                       ;;
-               "ext2"|"ext3"|"ext4")
-                       e2image -r "${POPULATE_METADUMP}" "${SCRATCH_DEV}" && return
-                       ;;
-               esac
+       echo "FSTYP ${FSTYP} MKFS_OPTIONS ${MKFS_OPTIONS} SIZE ${size} ${extra_descr} ARGS $@"
+}
+
+# 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
 
+       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}"
@@ -794,12 +876,19 @@ _scratch_populate_cached() {
        "xfs")
                _scratch_xfs_populate $@
                _scratch_xfs_populate_check
-               _scratch_metadump "${POPULATE_METADUMP}" -a -o
+               _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
-               e2image -Q "${SCRATCH_DEV}" "${POPULATE_METADUMP}"
+               _ext4_metadump "${SCRATCH_DEV}" "${POPULATE_METADUMP}" compress
                ;;
        *)
                _fail "Don't know how to populate a ${FSTYP} filesystem."