summaryrefslogtreecommitdiff
path: root/tests/xfs/444
blob: db7418c55deed315e18ce03a6d0bb47f7d65822f (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
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2018 Oracle, Inc.
#
# FS QA Test No. 444
#
# Make sure XFS can fix a v5 AGFL that wraps over the last block.
# Refer to commit 96f859d52bcb ("libxfs: pack the agfl header structure so
# XFS_AGFL_SIZE is correct") for details on the original on-disk format error
# and the patch "xfs: detect agfl count corruption and reset agfl") for details
# about the fix.
#
. ./common/preamble
_begin_fstest auto quick prealloc

_register_cleanup "_cleanup; rm -f $tmp.*"

# Import common functions.
. ./common/filter

# real QA test starts here
_supported_fs xfs

_require_check_dmesg
_require_scratch
_require_test_program "punch-alternating"
_require_xfs_io_command "falloc"
_require_xfs_db_write_array

# This is only a v5 filesystem problem
_require_scratch_xfs_crc

# Disable the scratch rt device to avoid test failures relating to the rt
# bitmap consuming free space in our small data device and throwing off the
# filestreams allocator.
unset SCRATCH_RTDEV

mount_loop() {
	if ! _try_scratch_mount >> $seqres.full 2>&1; then
		echo "scratch mount failed" >> $seqres.full
		return
	fi

	# Trigger agfl fixing by fragmenting free space enough to cause
	# a bnobt split
	blksz=$(_get_file_block_size ${SCRATCH_MNT})
	bno_maxrecs=$(( blksz / 8 ))
	filesz=$((bno_maxrecs * 3 * blksz))
	rm -rf $SCRATCH_MNT/a
	$XFS_IO_PROG -f -c "falloc 0 $filesz" $SCRATCH_MNT/a >> $seqres.full 2>&1
	test -e $SCRATCH_MNT/a && $here/src/punch-alternating $SCRATCH_MNT/a
	rm -rf $SCRATCH_MNT/a

	_scratch_unmount 2>&1 | _filter_scratch
}

dump_ag0() {
	_scratch_xfs_db -c 'sb 0' -c 'p' -c 'agf 0' -c 'p' -c 'agfl 0' -c 'p'
}

runtest() {
	cmd="$1"

	# Format filesystem
	echo "TEST $cmd" | _tee_kernlog
	echo "TEST $cmd" >> $seqres.full
	_scratch_mkfs >> $seqres.full

	# Record what was here before
	echo "FS BEFORE" >> $seqres.full
	dump_ag0 > $tmp.before
	cat $tmp.before >> $seqres.full

	sectsize=$(_scratch_xfs_get_metadata_field "sectsize" "sb 0")
	flfirst=$(_scratch_xfs_get_metadata_field "flfirst" "agf 0")
	fllast=$(_scratch_xfs_get_metadata_field "fllast" "agf 0")
	flcount=$(_scratch_xfs_get_metadata_field "flcount" "agf 0")

	# Due to a padding bug in the original v5 struct xfs_agfl,
	# XFS_AGFL_SIZE could be 36 on 32-bit or 40 on 64-bit.  On a system
	# with 512b sectors, this means that the AGFL length could be
	# ((512 - 36) / 4) = 119 entries on 32-bit or ((512 - 40) / 4) = 118
	# entries on 64-bit.
	#
	# We now have code to figure out if the AGFL list wraps incorrectly
	# according to the kernel's agfl size and fix it by resetting the agfl
	# to zero length.  Mutate ag 0's agfl to be in various configurations
	# and see if we can trigger the reset.
	#
	# Don't hardcode the numbers, calculate them.

	# Have to have at least three agfl items to test full wrap
	test "$flcount" -ge 3 || _notrun "insufficient agfl flcount"

	# mkfs should be able to make us a nice neat flfirst < fllast setup
	test "$flfirst" -lt "$fllast" || _notrun "fresh agfl already wrapped?"

	bad_agfl_size=$(( (sectsize - 40) / 4 ))
	good_agfl_size=$(( (sectsize - 36) / 4 ))
	agfl_size=
	case "$1" in
	"fix_end")	# fllast points to the end w/ 40-byte padding
		new_flfirst=$(( bad_agfl_size - flcount ))
		agfl_size=$bad_agfl_size;;
	"fix_start")	# flfirst points to the end w/ 40-byte padding
		new_flfirst=$(( bad_agfl_size - 1))
		agfl_size=$bad_agfl_size;;
	"fix_wrap")	# list wraps around end w/ 40-byte padding
		new_flfirst=$(( bad_agfl_size - (flcount / 2) ))
		agfl_size=$bad_agfl_size;;
	"start_zero")	# flfirst points to the start
		new_flfirst=0
		agfl_size=$good_agfl_size;;
	"good_end")	# fllast points to the end w/ 36-byte padding
		new_flfirst=$(( good_agfl_size - flcount ))
		agfl_size=$good_agfl_size;;
	"good_start")	# flfirst points to the end w/ 36-byte padding
		new_flfirst=$(( good_agfl_size - 1 ))
		agfl_size=$good_agfl_size;;
	"good_wrap")	# list wraps around end w/ 36-byte padding
		new_flfirst=$(( good_agfl_size - (flcount / 2) ))
		agfl_size=$good_agfl_size;;
	"bad_start")	# flfirst points off the end
		new_flfirst=$good_agfl_size
		agfl_size=$good_agfl_size;;
	"no_move")	# whatever mkfs formats (flfirst points to start)
		new_flfirst=$flfirst
		agfl_size=$good_agfl_size;;
	"simple_move")	# move list arbitrarily
		new_flfirst=$((fllast + 1))
		agfl_size=$good_agfl_size;;
	*)
		_fail "Internal test error";;
	esac
	new_fllast=$(( (new_flfirst + flcount - 1) % agfl_size ))

	# Log what we're doing...
	cat >> $seqres.full << ENDL
sector size: $sectsize
bad_agfl_size: $bad_agfl_size [0 - $((bad_agfl_size - 1))]
good_agfl_size: $good_agfl_size [0 - $((good_agfl_size - 1))]
agfl_size: $agfl_size
flfirst: $flfirst
fllast: $fllast
flcount: $flcount
new_flfirst: $new_flfirst
new_fllast: $new_fllast
ENDL

	# Remap the agfl blocks
	echo "$((good_agfl_size - 1)) 0xffffffff" > $tmp.remap
	seq "$flfirst" "$fllast" | while read f; do
		list_pos=$((f - flfirst))
		dest_pos=$(( (new_flfirst + list_pos) % agfl_size ))
		bno=$(_scratch_xfs_get_metadata_field "bno[$f]" "agfl 0")
		echo "$dest_pos $bno" >> $tmp.remap
	done

	cat $tmp.remap | while read dest_pos bno junk; do
		_scratch_xfs_set_metadata_field "bno[$dest_pos]" "$bno" \
				"agfl 0" >> $seqres.full
	done

	# Set new flfirst/fllast
	_scratch_xfs_set_metadata_field "fllast" "$new_fllast" \
			"agf 0" >> $seqres.full
	_scratch_xfs_set_metadata_field "flfirst" "$new_flfirst" \
			"agf 0" >> $seqres.full

	echo "FS AFTER" >> $seqres.full
	dump_ag0 > $tmp.corrupt 2> /dev/null
	diff -u $tmp.before $tmp.corrupt >> $seqres.full

	# Mount and see what happens
	mount_loop

	# Did we end up with a non-wrapped list?
	flfirst=$(_scratch_xfs_get_metadata_field "flfirst" "agf 0" 2>/dev/null)
	fllast=$(_scratch_xfs_get_metadata_field "fllast" "agf 0" 2>/dev/null)
	echo "flfirst=${flfirst} fllast=${fllast}" >> $seqres.full
	if [ "${flfirst}" -ge "$((good_agfl_size - 1))" ]; then
		echo "ASSERT flfirst < good_agfl_size - 1" | tee -a $seqres.full
	fi
	if [ "${fllast}" -ge "$((good_agfl_size - 1))" ]; then
		echo "ASSERT fllast < good_agfl_size - 1" | tee -a $seqres.full
	fi
	if [ "${flfirst}" -ge "${fllast}" ]; then
		echo "ASSERT flfirst < fllast" | tee -a $seqres.full
	fi

	echo "FS MOUNTLOOP" >> $seqres.full
	dump_ag0 > $tmp.mountloop 2> /dev/null
	diff -u $tmp.corrupt $tmp.mountloop >> $seqres.full

	# Let's see what repair thinks
	echo "REPAIR" >> $seqres.full
	_scratch_xfs_repair >> $seqres.full 2>&1

	echo "FS REPAIR" >> $seqres.full
	dump_ag0 > $tmp.repair 2> /dev/null
	diff -u $tmp.mountloop $tmp.repair >> $seqres.full

	# Exercise the filesystem again to make sure there aren't any lasting
	# ill effects from either the agfl reset or the recommended subsequent
	# repair run.
	mount_loop

	echo "FS REMOUNT" >> $seqres.full
	dump_ag0 > $tmp.remount 2> /dev/null
	diff -u $tmp.repair $tmp.remount >> $seqres.full
}

runtest fix_end
runtest fix_start
runtest fix_wrap
runtest start_zero
runtest good_end
runtest good_start
runtest good_wrap
runtest bad_start
runtest no_move
runtest simple_move

# Did we get the kernel warning too?
warn_str='WARNING: Reset corrupted AGFL'
_check_dmesg_for "${warn_str}" || echo "Missing dmesg string \"${warn_str}\"."

# Now run the regular dmesg check, filtering out the agfl warning
filter_agfl_reset_printk() {
	grep -v "${warn_str}"
}
_check_dmesg filter_agfl_reset_printk

status=0
exit