| ##/bin/bash |
| # SPDX-License-Identifier: GPL-2.0+ |
| # Copyright (c) 2016 Oracle. All Rights Reserved. |
| # |
| # Routines for injecting errors into filesystems |
| |
| . ./common/log |
| |
| # Tests whether $FSTYP is one of the filesystems that supports error injection |
| _require_error_injection() |
| { |
| case "$FSTYP" in |
| "xfs") |
| grep -q 'debug 1' /proc/fs/xfs/stat || \ |
| _notrun "XFS error injection requires CONFIG_XFS_DEBUG" |
| ;; |
| *) |
| _notrun "Error injection not supported on filesystem type: $FSTYP" |
| esac |
| } |
| |
| # Find the errortag injection knob in sysfs for a given xfs mount's |
| # block device. |
| _find_xfs_mountdev_errortag_knob() |
| { |
| dev="$1" |
| knob="$2" |
| shortdev="$(_short_dev "${dev}")" |
| tagfile="/sys/fs/xfs/${shortdev}/errortag/${knob}" |
| |
| # Some of the new sysfs errortag knobs were previously available via |
| # another sysfs path. |
| case "${knob}" in |
| "log_bad_crc") |
| if [ ! -w "${tagfile}" ]; then |
| tagfile="/sys/fs/xfs/${shortdev}/log/log_badcrc_factor" |
| fi |
| ;; |
| "drop_writes") |
| if [ ! -w "${tagfile}" ]; then |
| tagfile="/sys/fs/xfs/${shortdev}/drop_writes" |
| fi |
| if [ ! -w "${tagfile}" ]; then |
| tagfile="/sys/fs/xfs/${shortdev}/fail_writes" |
| fi |
| ;; |
| *) |
| ;; |
| esac |
| |
| echo "${tagfile}" |
| } |
| |
| # Requires that xfs_io inject command knows about this error type |
| _require_xfs_io_error_injection() |
| { |
| type="$1" |
| _require_error_injection |
| |
| # Can we find the error injection knobs via the new errortag |
| # configuration mechanism? |
| knob="$(_find_xfs_mountdev_errortag_knob "${TEST_DEV}" "${type}")" |
| test -w "${knob}" && return |
| |
| # If the directory containing the sysfs error injection knob exists |
| # but the knob itself isn't usable, this kernel doesn't know about |
| # the knob. Skip the test. |
| if [ -d "$(dirname "${knob}")" ]; then |
| _notrun "XFS error injection $type unknown on this kernel." |
| fi |
| |
| # NOTE: We can't actually test error injection here because xfs |
| # hasn't always range checked the argument to xfs_errortag_add. |
| # We also don't want to trip an error before we're ready to deal |
| # with it. |
| |
| $XFS_IO_PROG -x -c 'inject' $TEST_DIR | grep -q "$type" || \ |
| _notrun "XFS error injection $type unknown." |
| } |
| |
| # Inject an error into the test fs |
| _test_inject_error() |
| { |
| type="$1" |
| value="$2" |
| |
| knob="$(_find_xfs_mountdev_errortag_knob "${TEST_DEV}" "${type}")" |
| if [ -w "${knob}" ]; then |
| test -z "${value}" && value="default" |
| echo -n "${value}" > "${knob}" |
| elif [ -z "${value}" ] || [ "${value}" = "default" ]; then |
| $XFS_IO_PROG -x -c "inject $type" $TEST_DIR |
| else |
| _fail "Cannot inject error ${type} value ${value}." |
| fi |
| } |
| |
| # Inject an error into the scratch fs |
| _scratch_inject_error() |
| { |
| type="$1" |
| value="$2" |
| |
| knob="$(_find_xfs_mountdev_errortag_knob "${SCRATCH_DEV}" "${type}")" |
| if [ -w "${knob}" ]; then |
| test -z "${value}" && value="default" |
| echo -n "${value}" > "${knob}" |
| elif [ -z "${value}" ] || [ "${value}" = "default" ]; then |
| $XFS_IO_PROG -x -c "inject $type" $SCRATCH_MNT |
| else |
| _fail "Cannot inject error ${type} value ${value}." |
| fi |
| } |
| |
| # Requires the kernel fail_function facility (CONFIG_FUNCTION_ERROR_INJECTION |
| # and CONFIG_FAIL_FUNCTION) and, if a function name is given, that the function |
| # is annotated with ALLOW_ERROR_INJECTION(). |
| _require_function_error_injection() |
| { |
| local func="$1" |
| |
| _require_debugfs |
| test -d "$DEBUGFS_MNT/fail_function" || \ |
| _notrun "$DEBUGFS_MNT/fail_function not found; CONFIG_FAIL_FUNCTION not enabled" |
| |
| if [ -n "$func" ]; then |
| grep -qw "$func" "$DEBUGFS_MNT/error_injection/list" 2>/dev/null || \ |
| _notrun "$func is not registered for error injection" |
| fi |
| } |
| |
| # Inject a fixed return value into a kernel function via fail_function. |
| # $1 - function name (must be ALLOW_ERROR_INJECTION annotated) |
| # $2 - return value to inject (e.g. -12 for -ENOMEM) |
| # $3 - number of times to fail (default 1) |
| _inject_function_error() |
| { |
| local func="$1" |
| local retval="$2" |
| local times="${3:-1}" |
| |
| # Writing the function name to "inject" creates its per-function dir. |
| echo "$func" > "$DEBUGFS_MNT/fail_function/inject" |
| # The retval file is an unsigned hex attribute (DEFINE_DEBUGFS_ATTRIBUTE |
| # "%llx"), so a negative errno like -12 must be written as its unsigned |
| # 64-bit (two's-complement) hex form; a bare "-12" is rejected with EINVAL. |
| printf '%#x\n' "$retval" > "$DEBUGFS_MNT/fail_function/$func/retval" |
| echo 100 > "$DEBUGFS_MNT/fail_function/probability" |
| echo 0 > "$DEBUGFS_MNT/fail_function/interval" |
| echo 0 > "$DEBUGFS_MNT/fail_function/space" |
| echo 0 > "$DEBUGFS_MNT/fail_function/verbose" |
| echo "$times" > "$DEBUGFS_MNT/fail_function/times" |
| } |
| |
| # Stop injecting errors into a kernel function set up by |
| # _inject_function_error(). |
| _uninject_function_error() |
| { |
| local func="$1" |
| |
| echo 0 > "$DEBUGFS_MNT/fail_function/times" 2>/dev/null |
| echo 0 > "$DEBUGFS_MNT/fail_function/probability" 2>/dev/null |
| echo "!$func" > "$DEBUGFS_MNT/fail_function/inject" 2>/dev/null |
| } |