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
|
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2021 Oracle. All Rights Reserved.
#
# FS QA Test No. 185
#
# Regression test for commits:
#
# c02f6529864a ("xfs: make xfs_rtalloc_query_range input parameters const")
# 9ab72f222774 ("xfs: fix off-by-one error when the last rt extent is in use")
# 7e1826e05ba6 ("xfs: make fsmap backend function key parameters const")
#
# These commits fix a bug in fsmap where the data device fsmap function would
# corrupt the high key passed to the rt fsmap function if the data device
# number is smaller than the rt device number and the data device itself is
# smaller than the rt device.
#
. ./common/preamble
_begin_fstest auto fsmap prealloc punch
_cleanup()
{
cd /
rm -r -f $tmp.*
_scratch_unmount
test -n "$ddloop" && _destroy_loop_device "$ddloop"
test -n "$rtloop" && _destroy_loop_device "$rtloop"
test -n "$ddfile" && rm -f "$ddfile"
test -n "$rtfile" && rm -f "$rtfile"
test -n "$old_use_external" && USE_EXTERNAL="$old_use_external"
}
# real QA test starts here
_supported_fs xfs
_require_test
_require_loop
_require_xfs_io_command "falloc"
_require_xfs_io_command "fpunch"
_require_xfs_io_command "fsmap"
# Synthesize data and rt volumes that as needed to trigger the bug
ddfile=$TEST_DIR/data
rtfile=$TEST_DIR/rt
rm -f "$rtfile" "$ddfile"
ddsize="$((100 * 1048576))"
rtsize="$((200 * 1048576))"
$XFS_IO_PROG -f -c "truncate $ddsize" $ddfile
$XFS_IO_PROG -f -c "truncate $rtsize" $rtfile
ddloop="$(_create_loop_device $ddfile)"
rtloop="$(_create_loop_device $rtfile)"
test -z "$ddloop" && _fail "Could not create data loop device"
test -z "$rtloop" && _fail "Could not create rt loop device"
# dataloop must have a smaller device number than rtloop because fsmap sorts
# the output by device number
ddmajor=$(stat -c '%t' "$ddloop")
rtmajor=$(stat -c '%t' "$rtloop")
test $ddmajor -le $rtmajor || \
_notrun "Data loopdev major $ddmajor larger than rt major $rtmajor"
ddminor=$(stat -c '%T' "$ddloop")
rtminor=$(stat -c '%T' "$rtloop")
test $ddmajor -le $rtmajor || \
_notrun "Data loopdev minor $ddminor larger than rt minor $rtminor"
# Inject our custom-built devices as an rt-capable scratch device.
# We avoid touching "require_scratch" so that post-test fsck will not try to
# run on our synthesized scratch device.
old_use_external="$USE_EXTERNAL"
USE_EXTERNAL=yes
SCRATCH_RTDEV="$rtloop"
SCRATCH_DEV="$ddloop"
_scratch_mkfs >> $seqres.full
_try_scratch_mount >> $seqres.full || \
_notrun "mount with injected rt device failed"
# Create a file that we'll use to seed fsmap entries for the rt device,
# and force the root directory to create only data device files
rtfile="$SCRATCH_MNT/rtfile"
$XFS_IO_PROG -R -f -c 'stat -v' $rtfile | grep -q 'fsxattr.*xflags.*realtime' || \
_notrun "could not create realtime file"
$XFS_IO_PROG -c 'chattr -t' $SCRATCH_MNT
rtextsize="$(stat -c '%o' $rtfile)"
# Make sure the data device is smaller than the rt device by at least a few
# realtime extents.
ddbytes="$(stat -f -c '%S * %b' $SCRATCH_MNT | bc)"
rtbytes="$(stat -f -c '%S * %b' $rtfile | bc)"
test "$ddbytes" -lt "$((rtbytes + (10 * rtextsize) ))" || \
echo "data device ($ddbytes) has more bytes than rt ($rtbytes)"
# Craft the layout of the sole realtime file in such a way that the only
# allocated space on the realtime device is at a physical disk address that is
# higher than the size of the data device. For realtime files this is really
# easy because fallocate for the first rt file always starts allocating at
# physical offset zero.
alloc_rtx="$((rtbytes / rtextsize))"
$XFS_IO_PROG -c "falloc 0 $((alloc_rtx * rtextsize))" $rtfile
expected_end="$(( (alloc_rtx * rtextsize - 1) / 512 ))"
# Print extent mapping of rtfile in format:
# file_offset file_end physical_offset physical_end
rtfile_exts() {
$XFS_IO_PROG -c 'bmap -elp' $rtfile | \
grep -E -v '(^/|EXT:|hole)' | \
tr ':.[]' ' ' | \
while read junk foff fend physoff physend junk; do
echo "$foff $fend $physoff $physend"
done
}
# Make sure that we actually got the entire device.
rtfile_exts | $AWK_PROG -v end=$expected_end '
{
if ($3 != 0)
printf("%s: unexpected physical offset %s, wanted 0\n", $0, $3);
if ($4 != end)
printf("%s: unexpected physical end %s, wanted %d\n", $0, $4, end);
}'
# Now punch out a range that is slightly larger than the size of the data
# device.
punch_bytes="$((ddsize + rtextsize))"
punch_rtx="$((punch_bytes / rtextsize))"
$XFS_IO_PROG -c "fpunch 0 $((punch_rtx * rtextsize))" $rtfile
expected_offset="$((punch_rtx * rtextsize / 512))"
echo "rtfile should have physical extent from $expected_offset to $expected_end sectors" >> $seqres.full
# Make sure that the realtime file now has only one extent at the end of the
# realtime device
rtfile_exts | $AWK_PROG -v offset=$expected_offset -v end=$expected_end '
{
if ($3 != offset)
printf("%s: unexpected physical offset %s, wanted %d\n", $0, $3, offset);
if ($4 != end)
printf("%s: unexpected physical end %s, wanted %d\n", $0, $4, end);
}'
$XFS_IO_PROG -c 'bmap -elpv' $rtfile >> $seqres.full
$XFS_IO_PROG -c 'fsmap' $SCRATCH_MNT >> $seqres.full
fsmap() {
$XFS_IO_PROG -c 'fsmap' $SCRATCH_MNT | \
grep -v 'EXT:' | \
tr ':.[]' ' ' | \
while read junk major minor physoff physend junk; do
echo "$major:$minor $physoff $physend"
done
}
# Check the fsmap output contains a record for the realtime device at a
# physical offset higher than end of the data device and corresponding to the
# beginning of the non-punched area.
fsmap | $AWK_PROG -v dev="$rtmajor:$rtminor" -v offset=$expected_offset -v end=$expected_end '
BEGIN {
found_start = 0;
found_end = 0;
}
{
if ($1 == dev && $2 >= offset) {
found_start = 1;
if ($3 == end) {
found_end = 1;
}
}
}
END {
if (found_start == 0)
printf("No fsmap record for rtdev %s above sector %d\n", dev, offset);
if (found_end == 0)
printf("No fsmap record for rtdev %s ending at sector %d\n", dev, end);
}'
echo Silence is golden
# success, all done
status=0
exit
|