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