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