1#!/bin/ksh
2
3#
4# This file and its contents are supplied under the terms of the
5# Common Development and Distribution License ("CDDL"), version 1.0.
6# You may only use this file in accordance with the terms of version
7# 1.0 of the CDDL.
8#
9# A full copy of the text of the CDDL should have accompanied this
10# source.  A copy of the CDDL is also available via the Internet at
11# http://www.illumos.org/license/CDDL.
12#
13
14#
15# Copyright (c) 2021 by vStack. All rights reserved.
16#
17
18. "$STF_SUITE"/include/libtest.shlib
19. "$STF_SUITE"/include/blkdev.shlib
20
21#
22# Description:
23#
24# Test whether zhack label repair commands can recover detached devices
25# and corrupted checksums with a variety of sizes, and ensure
26# the purposes of either command is cleanly separated from the others.
27#
28# Strategy:
29#
30# Tests are done on loopback devices with sizes divisible by label size and sizes that are not.
31#
32# Test one:
33#
34# 1. Create pool on a loopback device with some test data
35# 2. Export the pool.
36# 3. Corrupt all label checksums in the pool
37# 4. Check that pool cannot be imported
38# 5. Verify that it cannot be imported after using zhack label repair -u
39#    to ensure that the -u option will quit on corrupted checksums.
40# 6. Use zhack label repair -c on device
41# 7. Check that pool can be imported and that data is intact
42#
43# Test two:
44#
45# 1. Create pool on a loopback device with some test data
46# 2. Detach either device from the mirror
47# 3. Export the pool
48# 4. Remove the non-detached device and its backing file
49# 5. Verify that the remaining detached device cannot be imported
50# 6. Verify that it cannot be imported after using zhack label repair -c
51#    to ensure that the -c option will not undetach a device.
52# 7. Use zhack label repair -u on device
53# 8. Verify that the detached device can be imported and that data is intact
54#
55# Test three:
56#
57# 1. Create pool on a loopback device with some test data
58# 2. Detach either device from the mirror
59# 3. Export the pool
60# 4. Remove the non-detached device and its backing file
61# 5. Corrupt all label checksums on the remaining device
62# 6. Verify that the remaining detached device cannot be imported
63# 7. Verify that it cannot be imported after using zhack label repair -u
64#    to ensure that the -u option will quit on corrupted checksums.
65# 8. Verify that it cannot be imported after using zhack label repair -c
66#    -c should repair the checksums, but not undetach a device.
67# 9. Use zhack label repair -u on device
68# 10. Verify that the detached device can be imported and that data is intact
69#
70# Test four:
71#
72# 1. Create pool on a loopback device with some test data
73# 2. Detach either device from the mirror
74# 3. Export the pool
75# 4. Remove the non-detached device and its backing file
76# 5. Corrupt all label checksums on the remaining device
77# 6. Verify that the remaining detached device cannot be imported
78# 7. Use zhack label repair -cu on device to attempt to fix checksums and
79#    undetach the device in a single operation.
80# 8. Verify that the detached device can be imported and that data is intact
81#
82
83log_assert "Verify zhack label repair <operation> <vdev> will repair label checksums and uberblocks"
84log_onexit cleanup
85
86LABEL_SIZE="$((2**18))"
87LABEL_NVLIST_END="$((LABEL_SIZE / 2))"
88LABEL_CKSUM_SIZE="32"
89LABEL_CKSUM_START="$(( LABEL_NVLIST_END - LABEL_CKSUM_SIZE ))"
90
91VIRTUAL_DISK=$TEST_BASE_DIR/disk
92VIRTUAL_MIRROR_DISK=$TEST_BASE_DIR/mirrordisk
93
94VIRTUAL_DEVICE=
95VIRTUAL_MIRROR_DEVICE=
96
97function cleanup_lo
98{
99	L_DEVICE="$1"
100
101	if [[ -e $L_DEVICE ]]; then
102		if is_linux; then
103			log_must losetup -d "$L_DEVICE"
104		elif is_freebsd; then
105			log_must mdconfig -d -u "$L_DEVICE"
106		else
107			log_must lofiadm -d "$L_DEVICE"
108		fi
109	fi
110}
111
112function cleanup
113{
114	poolexists "$TESTPOOL" && destroy_pool "$TESTPOOL"
115	cleanup_lo "$VIRTUAL_DEVICE"
116	cleanup_lo "$VIRTUAL_MIRROR_DEVICE"
117	VIRTUAL_DEVICE=
118	VIRTUAL_MIRROR_DEVICE=
119	[[ -f "$VIRTUAL_DISK" ]] && log_must rm "$VIRTUAL_DISK"
120	[[ -f "$VIRTUAL_MIRROR_DISK" ]] && log_must rm "$VIRTUAL_MIRROR_DISK"
121}
122
123RAND_MAX="$((2**15 - 1))"
124function get_devsize
125{
126	if [ "$RANDOM" -gt "$(( RAND_MAX / 2 ))" ]; then
127		echo "$(( MINVDEVSIZE + RANDOM ))"
128	else
129		echo "$MINVDEVSIZE"
130	fi
131}
132
133function pick_logop
134{
135	L_SHOULD_SUCCEED="$1"
136
137	l_logop="log_mustnot"
138	if [ "$L_SHOULD_SUCCEED" == true ]; then
139		l_logop="log_must"
140	fi
141
142	echo "$l_logop"
143}
144
145function check_dataset
146{
147	L_SHOULD_SUCCEED="$1"
148	L_LOGOP="$(pick_logop "$L_SHOULD_SUCCEED")"
149
150	"$L_LOGOP" mounted "$TESTPOOL"/"$TESTFS"
151
152	"$L_LOGOP" test -f "$TESTDIR"/"test"
153}
154
155function setup_dataset
156{
157	log_must zfs create "$TESTPOOL"/"$TESTFS"
158
159	log_must mkdir -p "$TESTDIR"
160	log_must zfs set mountpoint="$TESTDIR" "$TESTPOOL"/"$TESTFS"
161
162	log_must mounted "$TESTPOOL"/"$TESTFS"
163
164	log_must touch "$TESTDIR"/"test"
165	log_must test -f "$TESTDIR"/"test"
166
167	log_must zpool sync "$TESTPOOL"
168
169	check_dataset true
170}
171
172function get_practical_size
173{
174	L_SIZE="$1"
175
176	if [ "$((L_SIZE % LABEL_SIZE))" -ne 0 ]; then
177		echo "$(((L_SIZE / LABEL_SIZE) * LABEL_SIZE))"
178	else
179		echo "$L_SIZE"
180	fi
181}
182
183function corrupt_sized_label_checksum
184{
185	L_SIZE="$1"
186	L_LABEL="$2"
187	L_DEVICE="$3"
188
189	L_PRACTICAL_SIZE="$(get_practical_size "$L_SIZE")"
190
191	typeset -a L_OFFSETS=("$LABEL_CKSUM_START" \
192	    "$((LABEL_SIZE + LABEL_CKSUM_START))" \
193		"$(((L_PRACTICAL_SIZE - LABEL_SIZE*2) + LABEL_CKSUM_START))" \
194		"$(((L_PRACTICAL_SIZE - LABEL_SIZE) + LABEL_CKSUM_START))")
195
196	dd if=/dev/urandom of="$L_DEVICE" \
197	    seek="${L_OFFSETS["$L_LABEL"]}" bs=1 count="$LABEL_CKSUM_SIZE" \
198	    conv=notrunc
199}
200
201function corrupt_labels
202{
203	L_SIZE="$1"
204	L_DISK="$2"
205
206	corrupt_sized_label_checksum "$L_SIZE" 0 "$L_DISK"
207	corrupt_sized_label_checksum "$L_SIZE" 1 "$L_DISK"
208	corrupt_sized_label_checksum "$L_SIZE" 2 "$L_DISK"
209	corrupt_sized_label_checksum "$L_SIZE" 3 "$L_DISK"
210}
211
212function try_import_and_repair
213{
214	L_REPAIR_SHOULD_SUCCEED="$1"
215	L_IMPORT_SHOULD_SUCCEED="$2"
216	L_OP="$3"
217	L_POOLDISK="$4"
218	L_REPAIR_LOGOP="$(pick_logop "$L_REPAIR_SHOULD_SUCCEED")"
219	L_IMPORT_LOGOP="$(pick_logop "$L_IMPORT_SHOULD_SUCCEED")"
220
221	log_mustnot zpool import "$TESTPOOL" -d "$L_POOLDISK"
222
223	"$L_REPAIR_LOGOP" zhack label repair "$L_OP" "$L_POOLDISK"
224
225	"$L_IMPORT_LOGOP" zpool import "$TESTPOOL" -d "$L_POOLDISK"
226
227	check_dataset "$L_IMPORT_SHOULD_SUCCEED"
228}
229
230function prepare_vdev
231{
232	L_SIZE="$1"
233	L_BACKFILE="$2"
234
235	l_devname=
236	if truncate -s "$L_SIZE" "$L_BACKFILE"; then
237		if is_linux; then
238			l_devname="$(losetup -f "$L_BACKFILE" --show)"
239		elif is_freebsd; then
240			l_devname=/dev/"$(mdconfig -a -t vnode -f "$L_BACKFILE")"
241		else
242			l_devname="$(lofiadm -a "$L_BACKFILE")"
243		fi
244	fi
245	echo "$l_devname"
246}
247
248function run_test_one
249{
250	L_SIZE="$1"
251
252	VIRTUAL_DEVICE="$(prepare_vdev "$L_SIZE" "$VIRTUAL_DISK")"
253	log_must test -e "$VIRTUAL_DEVICE"
254
255	log_must zpool create "$TESTPOOL" "$VIRTUAL_DEVICE"
256
257	setup_dataset
258
259	log_must zpool export "$TESTPOOL"
260
261	corrupt_labels "$L_SIZE" "$VIRTUAL_DISK"
262
263	try_import_and_repair false false "-u" "$VIRTUAL_DEVICE"
264
265	try_import_and_repair true true "-c" "$VIRTUAL_DEVICE"
266
267	cleanup
268
269	log_pass "zhack label repair corruption test passed with a randomized size of $L_SIZE"
270}
271
272function make_mirrored_pool
273{
274	L_SIZE="$1"
275
276	VIRTUAL_DEVICE="$(prepare_vdev "$L_SIZE" "$VIRTUAL_DISK")"
277	log_must test -e "$VIRTUAL_DEVICE"
278	VIRTUAL_MIRROR_DEVICE="$(prepare_vdev "$L_SIZE" "$VIRTUAL_MIRROR_DISK")"
279	log_must test -e "$VIRTUAL_MIRROR_DEVICE"
280
281	log_must zpool create "$TESTPOOL" "$VIRTUAL_DEVICE"
282	log_must zpool attach "$TESTPOOL" "$VIRTUAL_DEVICE" "$VIRTUAL_MIRROR_DEVICE"
283}
284
285function export_and_cleanup_vdisk
286{
287	log_must zpool export "$TESTPOOL"
288
289	cleanup_lo "$VIRTUAL_DEVICE"
290
291	VIRTUAL_DEVICE=
292
293	log_must rm "$VIRTUAL_DISK"
294}
295
296function run_test_two
297{
298	L_SIZE="$1"
299
300	make_mirrored_pool "$L_SIZE"
301
302	setup_dataset
303
304	log_must zpool detach "$TESTPOOL" "$VIRTUAL_MIRROR_DEVICE"
305
306	export_and_cleanup_vdisk
307
308	try_import_and_repair false false "-c" "$VIRTUAL_MIRROR_DEVICE"
309
310	try_import_and_repair true true "-u" "$VIRTUAL_MIRROR_DEVICE"
311
312	cleanup
313
314	log_pass "zhack label repair detached test passed with a randomized size of $L_SIZE"
315}
316
317function run_test_three
318{
319	L_SIZE="$1"
320
321	make_mirrored_pool "$L_SIZE"
322
323	setup_dataset
324
325	log_must zpool detach "$TESTPOOL" "$VIRTUAL_MIRROR_DEVICE"
326
327	export_and_cleanup_vdisk
328
329	corrupt_labels "$L_SIZE" "$VIRTUAL_MIRROR_DISK"
330
331	try_import_and_repair false false "-u" "$VIRTUAL_MIRROR_DEVICE"
332
333	try_import_and_repair true false "-c" "$VIRTUAL_MIRROR_DEVICE"
334
335	try_import_and_repair true true "-u" "$VIRTUAL_MIRROR_DEVICE"
336
337	cleanup
338
339	log_pass "zhack label repair corruption and detached test passed with a randomized size of $L_SIZE"
340}
341
342function run_test_four
343{
344	L_SIZE="$1"
345
346	make_mirrored_pool "$L_SIZE"
347
348	setup_dataset
349
350	log_must zpool detach "$TESTPOOL" "$VIRTUAL_MIRROR_DEVICE"
351
352	export_and_cleanup_vdisk
353
354	corrupt_labels "$L_SIZE" "$VIRTUAL_MIRROR_DISK"
355
356	try_import_and_repair true true "-cu" "$VIRTUAL_MIRROR_DEVICE"
357
358	cleanup
359
360	log_pass "zhack label repair corruption and detached single-command test passed with a randomized size of $L_SIZE."
361}
362