fuzzy: capture core dumps from repair utilities
[xfstests-dev.git] / common / fuzzy
index c3b4dc7ea483a6f9c2896182a02ad025ebbe1d03..809dee5465c2d16df903b8004cd240219baf56f4 100644 (file)
@@ -1,24 +1,8 @@
 ##/bin/bash
-
-# Routines for fuzzing and scrubbing a filesystem.
-#
-#-----------------------------------------------------------------------
-#  Copyright (c) 2017 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.
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2017 Oracle.  All Rights Reserved.
 #
-#  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
-#-----------------------------------------------------------------------
+# Routines for fuzzing and scrubbing a filesystem.
 
 # Modify various files after a fuzzing operation
 _scratch_fuzz_modify() {
@@ -30,6 +14,8 @@ _scratch_fuzz_modify() {
        $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
+               # try to remove append, immutable (and even dax) flag if exists
+               $XFS_IO_PROG -rc 'chattr -x -i -a' "$f" > /dev/null 2>&1
                setfattr -n "user.date" -v "${date}" "$f"
                cat "/tmp/afile" >> "$f"
                mv "$f" "$f.longer"
@@ -39,7 +25,7 @@ _scratch_fuzz_modify() {
 
        echo "+++ create files"
        mkdir -p "${SCRATCH_MNT}/test.moo"
-       $XFS_IO_PROG -f -c 'pwrite -S 0x80 0 65536' "${SCRATCH_MNT}/test.moo/urk"
+       $XFS_IO_PROG -f -c 'pwrite -S 0x80 0 65536' "${SCRATCH_MNT}/test.moo/urk" > /dev/null
        sync
 
        echo "+++ remove files"
@@ -58,7 +44,7 @@ _scratch_fuzz_test() {
 # Do we have an online scrub program?
 _require_scrub() {
        case "${FSTYP}" in
-       "xfs"|"ext4")
+       "xfs")
                test -x "$XFS_SCRUB_PROG" || _notrun "xfs_scrub not found"
                ;;
        *)
@@ -70,7 +56,7 @@ _require_scrub() {
 # Scrub the scratch filesystem metadata (online)
 _scratch_scrub() {
        case "${FSTYP}" in
-       "xfs"|"ext4"|"ext3"|"ext2")
+       "xfs")
                $XFS_SCRUB_PROG -d -T -v "$@" $SCRATCH_MNT
                ;;
        *)
@@ -78,3 +64,256 @@ _scratch_scrub() {
                ;;
        esac
 }
+
+# Filter out any keys with an array index >= 10, collapse any array range
+# ("[1-195]") to the first item, and ignore padding fields.
+__filter_xfs_db_keys() {
+       sed -e '/\([a-z]*\)\[\([0-9][0-9]\+\)\].*/d' \
+           -e 's/\([a-zA-Z0-9_]*\)\[\([0-9]*\)-[0-9]*\]/\1[\2]/g' \
+           -e '/pad/d'
+}
+
+# Filter the xfs_db print command's field debug information
+# into field name and type.
+__filter_xfs_db_print_fields() {
+       filter="$1"
+       if [ -z "${filter}" ] || [ "${filter}" = "nofilter" ]; then
+               filter='^'
+       fi
+       grep ' = ' | while read key equals value; do
+               fuzzkey="$(echo "${key}" | __filter_xfs_db_keys)"
+               if [ -z "${fuzzkey}" ]; then
+                       continue
+               elif [[ "${value}" == "["* ]]; then
+                       echo "${value}" | sed -e 's/^.//g' -e 's/.$//g' -e 's/,/\n/g' | while read subfield; do
+                               echo "${fuzzkey}.${subfield}"
+                       done | __filter_xfs_db_keys
+               else
+                       echo "${fuzzkey}"
+               fi
+       done | egrep "${filter}"
+}
+
+# Navigate to some part of the filesystem and print the field info.
+# The first argument is an egrep filter for the fields
+# The rest of the arguments are xfs_db commands to locate the metadata.
+_scratch_xfs_list_metadata_fields() {
+       filter="$1"
+       shift
+       if [ -n "${SCRATCH_XFS_LIST_METADATA_FIELDS}" ]; then
+               echo "${SCRATCH_XFS_LIST_METADATA_FIELDS}" | tr '[ ,]' '[\n\n]'
+               return;
+       fi
+
+       local cmds=()
+       for arg in "$@"; do
+               cmds+=("-c" "${arg}")
+       done
+       _scratch_xfs_db "${cmds[@]}" -c print | __filter_xfs_db_print_fields "${filter}"
+}
+
+# Fuzz a metadata field
+# The first arg is the field name
+# The second arg is the xfs_db fuzz verb
+# The rest of the arguments are xfs_db commands to find the metadata.
+_scratch_xfs_fuzz_metadata_field() {
+       key="$1"
+       value="$2"
+       shift; shift
+
+       if [[ "${key}" == *crc ]]; then
+               fuzz_arg="-c"
+       else
+               fuzz_arg="-d"
+       fi
+       oldval="$(_scratch_xfs_get_metadata_field "${key}" "$@")"
+
+       local cmds=()
+       for arg in "$@"; do
+               cmds+=("-c" "${arg}")
+       done
+       while true; do
+               _scratch_xfs_db -x "${cmds[@]}" -c "fuzz ${fuzz_arg} ${key} ${value}"
+               echo
+               newval="$(_scratch_xfs_get_metadata_field "${key}" "$@" 2> /dev/null)"
+               if [ "${key}" != "random" ] || [ "${oldval}" != "${newval}" ]; then
+                       break;
+               fi
+       done
+       if [ "${oldval}" = "${newval}" ]; then
+               echo "Field ${key} already set to ${newval}, skipping test."
+               return 1
+       fi
+       return 0
+}
+
+# Try to forcibly unmount the scratch fs
+__scratch_xfs_fuzz_unmount()
+{
+       while _scratch_unmount 2>/dev/null; do sleep 0.2; done
+}
+
+# Restore metadata to scratch device prior to field-fuzzing.
+__scratch_xfs_fuzz_mdrestore()
+{
+       test -e "${POPULATE_METADUMP}" || _fail "Need to set POPULATE_METADUMP"
+
+       __scratch_xfs_fuzz_unmount
+       xfs_mdrestore "${POPULATE_METADUMP}" "${SCRATCH_DEV}"
+}
+
+__fuzz_notify() {
+       echo "$@"
+       test -w /dev/ttyprintk && echo "$@" >> /dev/ttyprintk
+}
+
+# Fuzz one field of some piece of metadata.
+# First arg is the field name
+# Second arg is the fuzz verb (ones, zeroes, random, add, sub...)
+# Third arg is the repair mode (online, offline, both, none)
+__scratch_xfs_fuzz_field_test() {
+       field="$1"
+       fuzzverb="$2"
+       repair="$3"
+       shift; shift; shift
+
+       # Set the new field value
+       __fuzz_notify "+ Fuzz ${field} = ${fuzzverb}"
+       echo "========================"
+       _scratch_xfs_fuzz_metadata_field "${field}" ${fuzzverb} "$@"
+       res=$?
+       test $res -ne 0 && return
+
+       # Try to catch the error with scrub
+       echo "+ Try to catch the error"
+       _try_scratch_mount 2>&1
+       res=$?
+       if [ $res -eq 0 ]; then
+               # Try an online scrub unless we're fuzzing ag 0's sb,
+               # which scrub doesn't know how to fix.
+               if [ "${repair}" != "none" ]; then
+                       echo "++ Online scrub"
+                       if [ "$1" != "sb 0" ]; then
+                               _scratch_scrub -n -a 1 -e continue 2>&1
+                               res=$?
+                               test $res -eq 0 && \
+                                       (>&2 echo "scrub didn't fail with ${field} = ${fuzzverb}.")
+                       fi
+               fi
+
+               # Try fixing the filesystem online?!
+               if [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
+                       __fuzz_notify "++ Try to repair filesystem online"
+                       _scratch_scrub 2>&1
+                       res=$?
+                       test $res -ne 0 && \
+                               (>&2 echo "online repair failed ($res) with ${field} = ${fuzzverb}.")
+               fi
+
+               __scratch_xfs_fuzz_unmount
+       elif [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
+               (>&2 echo "mount failed ($res) with ${field} = ${fuzzverb}.")
+       fi
+
+       # Repair the filesystem offline?
+       if [ "${repair}" = "offline" ] || [ "${repair}" = "both" ]; then
+               echo "+ Try to repair the filesystem offline"
+               _repair_scratch_fs 2>&1
+               res=$?
+               test $res -ne 0 && \
+                       (>&2 echo "offline repair failed ($res) with ${field} = ${fuzzverb}.")
+       fi
+
+       # See if repair finds a clean fs
+       if [ "${repair}" != "none" ]; then
+               echo "+ Make sure error is gone (offline)"
+               _scratch_xfs_repair -n 2>&1
+               res=$?
+               test $res -ne 0 && \
+                       (>&2 echo "offline re-scrub ($res) with ${field} = ${fuzzverb}.")
+       fi
+
+       # See if scrub finds a clean fs
+       echo "+ Make sure error is gone (online)"
+       _try_scratch_mount 2>&1
+       res=$?
+       if [ $res -eq 0 ]; then
+               # Try an online scrub unless we're fuzzing ag 0's sb,
+               # which scrub doesn't know how to fix.
+               if [ "${repair}" != "none" ]; then
+                       echo "++ Online scrub"
+                       if [ "$1" != "sb 0" ]; then
+                               _scratch_scrub -n -e continue 2>&1
+                               res=$?
+                               test $res -ne 0 && \
+                                       (>&2 echo "online re-scrub ($res) with ${field} = ${fuzzverb}.")
+                       fi
+               fi
+
+               # Try modifying the filesystem again!
+               __fuzz_notify "++ Try to write filesystem again"
+               _scratch_fuzz_modify 100 2>&1
+               __scratch_xfs_fuzz_unmount
+       else
+               (>&2 echo "re-mount failed ($res) with ${field} = ${fuzzverb}.")
+       fi
+
+       # See if repair finds a clean fs
+       if [ "${repair}" != "none" ]; then
+               echo "+ Re-check the filesystem (offline)"
+               _scratch_xfs_repair -n 2>&1
+               res=$?
+               test $res -ne 0 && \
+                       (>&2 echo "re-repair failed ($res) with ${field} = ${fuzzverb}.")
+       fi
+}
+
+# Make sure we have all the pieces we need for field fuzzing
+_require_scratch_xfs_fuzz_fields()
+{
+       _require_scratch_nocheck
+       _require_scrub
+       _require_populate_commands
+       _scratch_mkfs_xfs >/dev/null 2>&1
+       _require_xfs_db_command "fuzz"
+}
+
+# Grab the list of available fuzzing verbs
+_scratch_xfs_list_fuzz_verbs() {
+       if [ -n "${SCRATCH_XFS_LIST_FUZZ_VERBS}" ]; then
+               echo "${SCRATCH_XFS_LIST_FUZZ_VERBS}" | tr '[ ,]' '[\n\n]'
+               return;
+       fi
+       _scratch_xfs_db -x -c 'sb 0' -c 'fuzz' | grep '^Fuzz commands:' | \
+               sed -e 's/[,.]//g' -e 's/Fuzz commands: //g' -e 's/ /\n/g'
+}
+
+# Fuzz some of the fields of some piece of metadata
+# The first argument is an egrep filter for the field names
+# The second argument is the repair mode (online, offline, both)
+# The rest of the arguments are xfs_db commands to locate the metadata.
+#
+# Users can specify the fuzz verbs via SCRATCH_XFS_LIST_FUZZ_VERBS
+# They can specify the fields via SCRATCH_XFS_LIST_METADATA_FIELDS
+_scratch_xfs_fuzz_metadata() {
+       filter="$1"
+       repair="$2"
+       shift; shift
+
+       fields="$(_scratch_xfs_list_metadata_fields "${filter}" "$@")"
+       verbs="$(_scratch_xfs_list_fuzz_verbs)"
+       echo "Fields we propose to fuzz under: $@"
+       echo $(echo "${fields}")
+       echo "Verbs we propose to fuzz with:"
+       echo $(echo "${verbs}")
+
+       # Always capture full core dumps from crashing tools
+       ulimit -c unlimited
+
+       echo "${fields}" | while read field; do
+               echo "${verbs}" | while read fuzzverb; do
+                       __scratch_xfs_fuzz_mdrestore
+                       __scratch_xfs_fuzz_field_test "${field}" "${fuzzverb}" "${repair}" "$@"
+               done
+       done
+}