populate: discover XFS structure fields and fuzz verbs, and use them to fuzz fields
[xfstests-dev.git] / common / fuzzy
index c3b4dc7ea483a6f9c2896182a02ad025ebbe1d03..75b23e10d3f94591da7ad5048b01f977ec70348e 100644 (file)
@@ -78,3 +78,267 @@ _scratch_scrub() {
                ;;
        esac
 }
+
+# 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
+               # Filter out any keys with an array index >= 10, and
+               # collapse any array range ("[1-195]") to the first item.
+               fuzzkey="$(echo "${key}" | sed -e '/\([a-z]*\)\[\([0-9][0-9]\+\)\].*/d' -e 's/\([a-zA-Z0-9_]*\)\[\([0-9]*\)-[0-9]*\]/\1[\2]/g')"
+               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
+               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}"
+}
+
+# Get a metadata field
+# The first arg is the field name
+# The rest of the arguments are xfs_db commands to find the metadata.
+_scratch_xfs_get_metadata_field() {
+       key="$1"
+       shift
+
+       grep_key="$(echo "${key}" | tr '[]()' '....')"
+       local cmds=()
+       for arg in "$@"; do
+               cmds+=("-c" "${arg}")
+       done
+       _scratch_xfs_db "${cmds[@]}" -c "print ${key}" | grep "^${grep_key}" | \
+               sed -e 's/^.* = //g'
+}
+
+# Set a metadata field
+# The first arg is the field name
+# The second arg is the new value
+# The rest of the arguments are xfs_db commands to find the metadata.
+_scratch_xfs_set_metadata_field() {
+       key="$1"
+       value="$2"
+       shift; shift
+
+       local cmds=()
+       for arg in "$@"; do
+               cmds+=("-c" "${arg}")
+       done
+       _scratch_xfs_db -x "${cmds[@]}" -c "write -d ${key} ${value}"
+       echo
+}
+
+# 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
+       _scratch_xfs_db -x "${cmds[@]}" -c "fuzz ${fuzz_arg} ${key} ${value}"
+       echo
+       newval="$(_scratch_xfs_get_metadata_field "${key}" "$@" 2> /dev/null)"
+       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)
+__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"
+       _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.
+               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
+
+               # Try fixing the filesystem online?!
+               if [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
+                       __fuzz_notify "++ Try to repair filesystem online"
+                       _scratch_scrub -y 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
+       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}.")
+
+       # See if scrub finds a clean fs
+       echo "+ Make sure error is gone (online)"
+       _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.
+               echo "++ Online scrub"
+               if [ "$1" != "sb 0" ]; then
+                       _scratch_scrub -e continue 2>&1
+                       res=$?
+                       test $res -ne 0 && \
+                               (>&2 echo "online re-scrub ($res) with ${field} = ${fuzzverb}.")
+               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
+       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}.")
+}
+
+# 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 '^Verbs:' | \
+               sed -e 's/[,.]//g' -e 's/Verbs: //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}")
+
+       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
+}