| ##/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" |
| } |