blob: 3494b6dd3b9479e6120ca32717931044ccb1d847 [file] [log] [blame]
##/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"
}