##/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2015 Oracle. All Rights Reserved. # # common functions for setting up and tearing down a dmerror device _dmerror_setup_vars() { local backing_dev="$1" local tag="$2" local target="$3" test -z "$target" && target=error local blk_dev_size=$(blockdev --getsz "$backing_dev") eval export "DMLINEAR_${tag}TABLE=\"0 $blk_dev_size linear $backing_dev 0\"" eval export "DMERROR_${tag}TABLE=\"0 $blk_dev_size $target $backing_dev 0\"" } _dmerror_setup() { local rt_target= local log_target= for arg in "$@"; do case "${arg}" in no_rt) rt_target=linear;; no_log) log_target=linear;; *) echo "${arg}: Unknown _dmerror_setup arg.";; esac done # Scratch device export DMERROR_DEV='/dev/mapper/error-test' _dmerror_setup_vars $SCRATCH_DEV # Realtime device. We reassign SCRATCH_RTDEV so that all the scratch # helpers continue to work unmodified. if [ -n "$SCRATCH_RTDEV" ]; then if [ -z "$NON_ERROR_RTDEV" ]; then # Set up the device switch local dm_backing_dev=$SCRATCH_RTDEV export NON_ERROR_RTDEV="$SCRATCH_RTDEV" SCRATCH_RTDEV='/dev/mapper/error-rttest' else # Already set up; recreate tables local dm_backing_dev="$NON_ERROR_RTDEV" fi _dmerror_setup_vars $dm_backing_dev RT $rt_target fi # External log device. We reassign SCRATCH_LOGDEV so that all the # scratch helpers continue to work unmodified. if [ -n "$SCRATCH_LOGDEV" ]; then if [ -z "$NON_ERROR_LOGDEV" ]; then # Set up the device switch local dm_backing_dev=$SCRATCH_LOGDEV export NON_ERROR_LOGDEV="$SCRATCH_LOGDEV" SCRATCH_LOGDEV='/dev/mapper/error-logtest' else # Already set up; recreate tables local dm_backing_dev="$NON_ERROR_LOGDEV" fi _dmerror_setup_vars $dm_backing_dev LOG $log_target fi } _dmerror_init() { _dmerror_setup "$@" _dmsetup_remove error-test _dmsetup_create error-test --table "$DMLINEAR_TABLE" || \ _fatal "failed to create dm linear device" if [ -n "$NON_ERROR_RTDEV" ]; then _dmsetup_remove error-rttest _dmsetup_create error-rttest --table "$DMLINEAR_RTTABLE" || \ _fatal "failed to create dm linear rt device" fi if [ -n "$NON_ERROR_LOGDEV" ]; then _dmsetup_remove error-logtest _dmsetup_create error-logtest --table "$DMLINEAR_LOGTABLE" || \ _fatal "failed to create dm linear log device" fi } _dmerror_mount() { _scratch_options mount $MOUNT_PROG -t $FSTYP `_common_dev_mount_options $*` $SCRATCH_OPTIONS \ $DMERROR_DEV $SCRATCH_MNT } _dmerror_unmount() { umount $SCRATCH_MNT } _dmerror_cleanup() { test -n "$NON_ERROR_LOGDEV" && $DMSETUP_PROG resume error-logtest &>/dev/null test -n "$NON_ERROR_RTDEV" && $DMSETUP_PROG resume error-rttest &>/dev/null $DMSETUP_PROG resume error-test > /dev/null 2>&1 $UMOUNT_PROG $SCRATCH_MNT > /dev/null 2>&1 test -n "$NON_ERROR_LOGDEV" && _dmsetup_remove error-logtest test -n "$NON_ERROR_RTDEV" && _dmsetup_remove error-rttest _dmsetup_remove error-test unset DMERROR_DEV DMLINEAR_TABLE DMERROR_TABLE if [ -n "$NON_ERROR_LOGDEV" ]; then SCRATCH_LOGDEV="$NON_ERROR_LOGDEV" unset NON_ERROR_LOGDEV DMLINEAR_LOGTABLE DMERROR_LOGTABLE fi if [ -n "$NON_ERROR_RTDEV" ]; then SCRATCH_RTDEV="$NON_ERROR_RTDEV" unset NON_ERROR_RTDEV DMLINEAR_RTTABLE DMERROR_RTTABLE fi } _dmerror_load_error_table() { local load_res=0 local resume_res=0 suspend_opt="--nolockfs" if [ "$1" = "lockfs" ]; then suspend_opt="" elif [ -n "$*" ]; then suspend_opt="$*" fi # If the full environment is set up, configure ourselves for shutdown type _prepare_for_eio_shutdown &>/dev/null && \ _prepare_for_eio_shutdown $DMERROR_DEV # Suspend the scratch device before the log and realtime devices so # that the kernel can freeze and flush the filesystem if the caller # wanted a freeze. $DMSETUP_PROG suspend $suspend_opt error-test [ $? -ne 0 ] && _fail "dmsetup suspend failed" if [ -n "$NON_ERROR_RTDEV" ]; then $DMSETUP_PROG suspend $suspend_opt error-rttest [ $? -ne 0 ] && _fail "failed to suspend error-rttest" fi if [ -n "$NON_ERROR_LOGDEV" ]; then $DMSETUP_PROG suspend $suspend_opt error-logtest [ $? -ne 0 ] && _fail "failed to suspend error-logtest" fi # Load new table $DMSETUP_PROG load error-test --table "$DMERROR_TABLE" load_res=$? if [ -n "$NON_ERROR_RTDEV" ]; then $DMSETUP_PROG load error-rttest --table "$DMERROR_RTTABLE" [ $? -ne 0 ] && _fail "failed to load error table into error-rttest" fi if [ -n "$NON_ERROR_LOGDEV" ]; then $DMSETUP_PROG load error-logtest --table "$DMERROR_LOGTABLE" [ $? -ne 0 ] && _fail "failed to load error table into error-logtest" fi # Resume devices in the opposite order that we suspended them. if [ -n "$NON_ERROR_LOGDEV" ]; then $DMSETUP_PROG resume error-logtest [ $? -ne 0 ] && _fail "failed to resume error-logtest" fi if [ -n "$NON_ERROR_RTDEV" ]; then $DMSETUP_PROG resume error-rttest [ $? -ne 0 ] && _fail "failed to resume error-rttest" fi $DMSETUP_PROG resume error-test resume_res=$? [ $load_res -ne 0 ] && _fail "dmsetup failed to load error table" [ $resume_res -ne 0 ] && _fail "dmsetup resume failed" } _dmerror_load_working_table() { local load_res=0 local resume_res=0 suspend_opt="--nolockfs" if [ "$1" = "lockfs" ]; then suspend_opt="" elif [ -n "$*" ]; then suspend_opt="$*" fi # Suspend the scratch device before the log and realtime devices so # that the kernel can freeze and flush the filesystem if the caller # wanted a freeze. $DMSETUP_PROG suspend $suspend_opt error-test [ $? -ne 0 ] && _fail "dmsetup suspend failed" if [ -n "$NON_ERROR_RTDEV" ]; then $DMSETUP_PROG suspend $suspend_opt error-rttest [ $? -ne 0 ] && _fail "failed to suspend error-rttest" fi if [ -n "$NON_ERROR_LOGDEV" ]; then $DMSETUP_PROG suspend $suspend_opt error-logtest [ $? -ne 0 ] && _fail "failed to suspend error-logtest" fi # Load new table $DMSETUP_PROG load error-test --table "$DMLINEAR_TABLE" load_res=$? if [ -n "$NON_ERROR_RTDEV" ]; then $DMSETUP_PROG load error-rttest --table "$DMLINEAR_RTTABLE" [ $? -ne 0 ] && _fail "failed to load working table into error-rttest" fi if [ -n "$NON_ERROR_LOGDEV" ]; then $DMSETUP_PROG load error-logtest --table "$DMLINEAR_LOGTABLE" [ $? -ne 0 ] && _fail "failed to load working table into error-logtest" fi # Resume devices in the opposite order that we suspended them. if [ -n "$NON_ERROR_LOGDEV" ]; then $DMSETUP_PROG resume error-logtest [ $? -ne 0 ] && _fail "failed to resume error-logtest" fi if [ -n "$NON_ERROR_RTDEV" ]; then $DMSETUP_PROG resume error-rttest [ $? -ne 0 ] && _fail "failed to resume error-rttest" fi $DMSETUP_PROG resume error-test resume_res=$? [ $load_res -ne 0 ] && _fail "dmsetup failed to load error table" [ $resume_res -ne 0 ] && _fail "dmsetup resume failed" } # Given a list of (start, length) tuples on stdin, combine adjacent tuples into # larger ones and write the new list to stdout. __dmerror_combine_extents() { local awk_program=' BEGIN { start = 0; len = 0; } { if (start + len == $1) { len += $2; } else { if (len > 0) printf("%d %d\n", start, len); start = $1; len = $2; } } END { if (len > 0) printf("%d %d\n", start, len); }' awk "$awk_program" } # Given a block device, the name of a preferred dm target, the name of an # implied dm target, and a list of (start, len) tuples on stdin, create a new # dm table which maps each of the tuples to the preferred target and all other # areas to the implied dm target. __dmerror_recreate_map() { local device="$1" local preferred_tgt="$2" local implied_tgt="$3" local size=$(blockdev --getsz "$device") local awk_program=' BEGIN { implied_start = 0; } { extent_start = $1; extent_len = $2; if (extent_start > size) { extent_start = size; extent_len = 0; } else if (extent_start + extent_len > size) { extent_len = size - extent_start; } if (implied_start < extent_start) printf("%d %d %s %s %d\n", implied_start, extent_start - implied_start, implied_tgt, device, implied_start); printf("%d %d %s %s %d\n", extent_start, extent_len, preferred_tgt, device, extent_start); implied_start = extent_start + extent_len; } END { if (implied_start < size) printf("%d %d %s %s %d\n", implied_start, size - implied_start, implied_tgt, device, implied_start); }' awk -v device="$device" -v size=$size -v implied_tgt="$implied_tgt" \ -v preferred_tgt="$preferred_tgt" "$awk_program" } # Update the dm error table so that the range (start, len) maps to the # preferred dm target, overriding anything that maps to the implied dm target. # This assumes that the only desired targets for this dm device are the # preferred and and implied targets. The fifth argument is the scratch device # that we want to change the table for. __dmerror_change() { local start="$1" local len="$2" local preferred_tgt="$3" local implied_tgt="$4" local whichdev="$5" local old_table local new_table case "$whichdev" in "SCRATCH_DEV"|"") whichdev="$SCRATCH_DEV";; "SCRATCH_LOGDEV"|"LOG") whichdev="$NON_ERROR_LOGDEV";; "SCRATCH_RTDEV"|"RT") whichdev="$NON_ERROR_RTDEV";; esac case "$whichdev" in "$SCRATCH_DEV") old_table="$DMERROR_TABLE";; "$NON_ERROR_LOGDEV") old_table="$DMERROR_LOGTABLE";; "$NON_ERROR_RTDEV") old_table="$DMERROR_RTTABLE";; *) echo "$whichdev: Unknown dmerror device." return ;; esac new_table="$( (echo "$old_table"; echo "$start $len $preferred_tgt") | \ awk -v type="$preferred_tgt" '{if ($3 == type) print $0;}' | \ sort -g | \ __dmerror_combine_extents | \ __dmerror_recreate_map "$whichdev" "$preferred_tgt" \ "$implied_tgt" )" case "$whichdev" in "$SCRATCH_DEV") DMERROR_TABLE="$new_table";; "$NON_ERROR_LOGDEV") DMERROR_LOGTABLE="$new_table";; "$NON_ERROR_RTDEV") DMERROR_RTTABLE="$new_table";; esac } # Reset the dm error table to everything ok. The dm device itself must be # remapped by calling _dmerror_load_error_table. _dmerror_reset_table() { DMERROR_TABLE="$DMLINEAR_TABLE" DMERROR_LOGTABLE="$DMLINEAR_LOGTABLE" DMERROR_RTTABLE="$DMLINEAR_RTTABLE" } # Update the dm error table so that IOs to the given range will return EIO. # The dm device itself must be remapped by calling _dmerror_load_error_table. _dmerror_mark_range_bad() { local start="$1" local len="$2" local dev="$3" __dmerror_change "$start" "$len" error linear "$dev" } # Update the dm error table so that IOs to the given range will succeed. # The dm device itself must be remapped by calling _dmerror_load_error_table. _dmerror_mark_range_good() { local start="$1" local len="$2" local dev="$3" __dmerror_change "$start" "$len" linear error "$dev" }