summaryrefslogtreecommitdiff
path: root/common/dmerror
blob: 3494b6dd3b9479e6120ca32717931044ccb1d847 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
##/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"
}