common/dmerror: always try to resume device
[xfstests-dev.git] / common / fuzzy
1 ##/bin/bash
2 # SPDX-License-Identifier: GPL-2.0+
3 # Copyright (c) 2017 Oracle.  All Rights Reserved.
4 #
5 # Routines for fuzzing and scrubbing a filesystem.
6
7 # Modify various files after a fuzzing operation
8 _scratch_fuzz_modify() {
9         nr="$1"
10
11         test -z "${nr}" && nr=50000
12         echo "+++ touch ${nr} files"
13         blk_sz=$(stat -f -c '%s' ${SCRATCH_MNT})
14         $XFS_IO_PROG -f -c "pwrite -S 0x63 0 ${blk_sz}" "/tmp/afile" > /dev/null
15         date="$(date)"
16         find "${SCRATCH_MNT}/" -type f 2> /dev/null | head -n "${nr}" | while read f; do
17                 setfattr -n "user.date" -v "${date}" "$f"
18                 cat "/tmp/afile" >> "$f"
19                 mv "$f" "$f.longer"
20         done
21         sync
22         rm -rf "/tmp/afile"
23
24         echo "+++ create files"
25         mkdir -p "${SCRATCH_MNT}/test.moo"
26         $XFS_IO_PROG -f -c 'pwrite -S 0x80 0 65536' "${SCRATCH_MNT}/test.moo/urk" > /dev/null
27         sync
28
29         echo "+++ remove files"
30         rm -rf "${SCRATCH_MNT}/test.moo"
31 }
32
33 # Try to access files after fuzzing
34 _scratch_fuzz_test() {
35         echo "+++ ls -laR" >> $seqres.full
36         ls -laR "${SCRATCH_MNT}/test.1/" >/dev/null 2>&1
37
38         echo "+++ cat files" >> $seqres.full
39         (find "${SCRATCH_MNT}/test.1/" -type f -size -1048576k -print0 | xargs -0 cat) >/dev/null 2>&1
40 }
41
42 # Do we have an online scrub program?
43 _require_scrub() {
44         case "${FSTYP}" in
45         "xfs")
46                 test -x "$XFS_SCRUB_PROG" || _notrun "xfs_scrub not found"
47                 ;;
48         *)
49                 _notrun "No online scrub program for ${FSTYP}."
50                 ;;
51         esac
52 }
53
54 # Scrub the scratch filesystem metadata (online)
55 _scratch_scrub() {
56         case "${FSTYP}" in
57         "xfs")
58                 $XFS_SCRUB_PROG -d -T -v "$@" $SCRATCH_MNT
59                 ;;
60         *)
61                 _fail "No online scrub program for ${FSTYP}."
62                 ;;
63         esac
64 }
65
66 # Filter out any keys with an array index >= 10, collapse any array range
67 # ("[1-195]") to the first item, and ignore padding fields.
68 __filter_xfs_db_keys() {
69         sed -e '/\([a-z]*\)\[\([0-9][0-9]\+\)\].*/d' \
70             -e 's/\([a-zA-Z0-9_]*\)\[\([0-9]*\)-[0-9]*\]/\1[\2]/g' \
71             -e '/pad/d'
72 }
73
74 # Filter the xfs_db print command's field debug information
75 # into field name and type.
76 __filter_xfs_db_print_fields() {
77         filter="$1"
78         if [ -z "${filter}" ] || [ "${filter}" = "nofilter" ]; then
79                 filter='^'
80         fi
81         grep ' = ' | while read key equals value; do
82                 fuzzkey="$(echo "${key}" | __filter_xfs_db_keys)"
83                 if [ -z "${fuzzkey}" ]; then
84                         continue
85                 elif [[ "${value}" == "["* ]]; then
86                         echo "${value}" | sed -e 's/^.//g' -e 's/.$//g' -e 's/,/\n/g' | while read subfield; do
87                                 echo "${fuzzkey}.${subfield}"
88                         done | __filter_xfs_db_keys
89                 else
90                         echo "${fuzzkey}"
91                 fi
92         done | egrep "${filter}"
93 }
94
95 # Navigate to some part of the filesystem and print the field info.
96 # The first argument is an egrep filter for the fields
97 # The rest of the arguments are xfs_db commands to locate the metadata.
98 _scratch_xfs_list_metadata_fields() {
99         filter="$1"
100         shift
101         if [ -n "${SCRATCH_XFS_LIST_METADATA_FIELDS}" ]; then
102                 echo "${SCRATCH_XFS_LIST_METADATA_FIELDS}" | tr '[ ,]' '[\n\n]'
103                 return;
104         fi
105
106         local cmds=()
107         for arg in "$@"; do
108                 cmds+=("-c" "${arg}")
109         done
110         _scratch_xfs_db "${cmds[@]}" -c print | __filter_xfs_db_print_fields "${filter}"
111 }
112
113 # Fuzz a metadata field
114 # The first arg is the field name
115 # The second arg is the xfs_db fuzz verb
116 # The rest of the arguments are xfs_db commands to find the metadata.
117 _scratch_xfs_fuzz_metadata_field() {
118         key="$1"
119         value="$2"
120         shift; shift
121
122         if [[ "${key}" == *crc ]]; then
123                 fuzz_arg="-c"
124         else
125                 fuzz_arg="-d"
126         fi
127         oldval="$(_scratch_xfs_get_metadata_field "${key}" "$@")"
128
129         local cmds=()
130         for arg in "$@"; do
131                 cmds+=("-c" "${arg}")
132         done
133         while true; do
134                 _scratch_xfs_db -x "${cmds[@]}" -c "fuzz ${fuzz_arg} ${key} ${value}"
135                 echo
136                 newval="$(_scratch_xfs_get_metadata_field "${key}" "$@" 2> /dev/null)"
137                 if [ "${key}" != "random" ] || [ "${oldval}" != "${newval}" ]; then
138                         break;
139                 fi
140         done
141         if [ "${oldval}" = "${newval}" ]; then
142                 echo "Field ${key} already set to ${newval}, skipping test."
143                 return 1
144         fi
145         return 0
146 }
147
148 # Try to forcibly unmount the scratch fs
149 __scratch_xfs_fuzz_unmount()
150 {
151         while _scratch_unmount 2>/dev/null; do sleep 0.2; done
152 }
153
154 # Restore metadata to scratch device prior to field-fuzzing.
155 __scratch_xfs_fuzz_mdrestore()
156 {
157         test -e "${POPULATE_METADUMP}" || _fail "Need to set POPULATE_METADUMP"
158
159         __scratch_xfs_fuzz_unmount
160         xfs_mdrestore "${POPULATE_METADUMP}" "${SCRATCH_DEV}"
161 }
162
163 __fuzz_notify() {
164         echo "$@"
165         test -w /dev/ttyprintk && echo "$@" >> /dev/ttyprintk
166 }
167
168 # Fuzz one field of some piece of metadata.
169 # First arg is the field name
170 # Second arg is the fuzz verb (ones, zeroes, random, add, sub...)
171 # Third arg is the repair mode (online, offline, both, none)
172 __scratch_xfs_fuzz_field_test() {
173         field="$1"
174         fuzzverb="$2"
175         repair="$3"
176         shift; shift; shift
177
178         # Set the new field value
179         __fuzz_notify "+ Fuzz ${field} = ${fuzzverb}"
180         echo "========================"
181         _scratch_xfs_fuzz_metadata_field "${field}" ${fuzzverb} "$@"
182         res=$?
183         test $res -ne 0 && return
184
185         # Try to catch the error with scrub
186         echo "+ Try to catch the error"
187         _try_scratch_mount 2>&1
188         res=$?
189         if [ $res -eq 0 ]; then
190                 # Try an online scrub unless we're fuzzing ag 0's sb,
191                 # which scrub doesn't know how to fix.
192                 if [ "${repair}" != "none" ]; then
193                         echo "++ Online scrub"
194                         if [ "$1" != "sb 0" ]; then
195                                 _scratch_scrub -n -a 1 -e continue 2>&1
196                                 res=$?
197                                 test $res -eq 0 && \
198                                         (>&2 echo "scrub didn't fail with ${field} = ${fuzzverb}.")
199                         fi
200                 fi
201
202                 # Try fixing the filesystem online?!
203                 if [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
204                         __fuzz_notify "++ Try to repair filesystem online"
205                         _scratch_scrub 2>&1
206                         res=$?
207                         test $res -ne 0 && \
208                                 (>&2 echo "online repair failed ($res) with ${field} = ${fuzzverb}.")
209                 fi
210
211                 __scratch_xfs_fuzz_unmount
212         elif [ "${repair}" = "online" ] || [ "${repair}" = "both" ]; then
213                 (>&2 echo "mount failed ($res) with ${field} = ${fuzzverb}.")
214         fi
215
216         # Repair the filesystem offline?
217         if [ "${repair}" = "offline" ] || [ "${repair}" = "both" ]; then
218                 echo "+ Try to repair the filesystem offline"
219                 _repair_scratch_fs 2>&1
220                 res=$?
221                 test $res -ne 0 && \
222                         (>&2 echo "offline repair failed ($res) with ${field} = ${fuzzverb}.")
223         fi
224
225         # See if repair finds a clean fs
226         if [ "${repair}" != "none" ]; then
227                 echo "+ Make sure error is gone (offline)"
228                 _scratch_xfs_repair -n 2>&1
229                 res=$?
230                 test $res -ne 0 && \
231                         (>&2 echo "offline re-scrub ($res) with ${field} = ${fuzzverb}.")
232         fi
233
234         # See if scrub finds a clean fs
235         echo "+ Make sure error is gone (online)"
236         _try_scratch_mount 2>&1
237         res=$?
238         if [ $res -eq 0 ]; then
239                 # Try an online scrub unless we're fuzzing ag 0's sb,
240                 # which scrub doesn't know how to fix.
241                 if [ "${repair}" != "none" ]; then
242                         echo "++ Online scrub"
243                         if [ "$1" != "sb 0" ]; then
244                                 _scratch_scrub -n -e continue 2>&1
245                                 res=$?
246                                 test $res -ne 0 && \
247                                         (>&2 echo "online re-scrub ($res) with ${field} = ${fuzzverb}.")
248                         fi
249                 fi
250
251                 # Try modifying the filesystem again!
252                 __fuzz_notify "++ Try to write filesystem again"
253                 _scratch_fuzz_modify 100 2>&1
254                 __scratch_xfs_fuzz_unmount
255         else
256                 (>&2 echo "re-mount failed ($res) with ${field} = ${fuzzverb}.")
257         fi
258
259         # See if repair finds a clean fs
260         if [ "${repair}" != "none" ]; then
261                 echo "+ Re-check the filesystem (offline)"
262                 _scratch_xfs_repair -n 2>&1
263                 res=$?
264                 test $res -ne 0 && \
265                         (>&2 echo "re-repair failed ($res) with ${field} = ${fuzzverb}.")
266         fi
267 }
268
269 # Make sure we have all the pieces we need for field fuzzing
270 _require_scratch_xfs_fuzz_fields()
271 {
272         _require_scratch_nocheck
273         _require_scrub
274         _require_populate_commands
275         _scratch_mkfs_xfs >/dev/null 2>&1
276         _require_xfs_db_command "fuzz"
277 }
278
279 # Grab the list of available fuzzing verbs
280 _scratch_xfs_list_fuzz_verbs() {
281         if [ -n "${SCRATCH_XFS_LIST_FUZZ_VERBS}" ]; then
282                 echo "${SCRATCH_XFS_LIST_FUZZ_VERBS}" | tr '[ ,]' '[\n\n]'
283                 return;
284         fi
285         _scratch_xfs_db -x -c 'sb 0' -c 'fuzz' | grep '^Fuzz commands:' | \
286                 sed -e 's/[,.]//g' -e 's/Fuzz commands: //g' -e 's/ /\n/g'
287 }
288
289 # Fuzz some of the fields of some piece of metadata
290 # The first argument is an egrep filter for the field names
291 # The second argument is the repair mode (online, offline, both)
292 # The rest of the arguments are xfs_db commands to locate the metadata.
293 #
294 # Users can specify the fuzz verbs via SCRATCH_XFS_LIST_FUZZ_VERBS
295 # They can specify the fields via SCRATCH_XFS_LIST_METADATA_FIELDS
296 _scratch_xfs_fuzz_metadata() {
297         filter="$1"
298         repair="$2"
299         shift; shift
300
301         fields="$(_scratch_xfs_list_metadata_fields "${filter}" "$@")"
302         verbs="$(_scratch_xfs_list_fuzz_verbs)"
303         echo "Fields we propose to fuzz under: $@"
304         echo $(echo "${fields}")
305         echo "Verbs we propose to fuzz with:"
306         echo $(echo "${verbs}")
307
308         echo "${fields}" | while read field; do
309                 echo "${verbs}" | while read fuzzverb; do
310                         __scratch_xfs_fuzz_mdrestore
311                         __scratch_xfs_fuzz_field_test "${field}" "${fuzzverb}" "${repair}" "$@"
312                 done
313         done
314 }