OSDN Git Service

Merge tag 'pull-halloween-omnibus-311023-2' of https://gitlab.com/stsquad/qemu into...
[qmiga/qemu.git] / tests / qemu-iotests / 308
1 #!/usr/bin/env bash
2 # group: rw
3 #
4 # Test FUSE exports (in ways that are not captured by the generic
5 # tests)
6 #
7 # Copyright (C) 2020 Red Hat, Inc.
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 seq=$(basename "$0")
24 echo "QA output created by $seq"
25
26 status=1        # failure is the default!
27
28 _cleanup()
29 {
30     _cleanup_qemu
31     _cleanup_test_img
32     rmdir "$EXT_MP" 2>/dev/null
33     rm -f "$EXT_MP"
34     rm -f "$COPIED_IMG"
35 }
36 trap "_cleanup; exit \$status" 0 1 2 3 15
37
38 # get standard environment, filters and checks
39 . ./common.rc
40 . ./common.filter
41 . ./common.qemu
42
43 # Generic format, but needs a plain filename
44 _supported_fmt generic
45 if [ "$IMGOPTSSYNTAX" = "true" ]; then
46     _unsupported_fmt $IMGFMT
47 fi
48 # We need the image to have exactly the specified size, and VPC does
49 # not allow that by default
50 _unsupported_fmt vpc
51
52 _supported_proto file # We create the FUSE export manually
53 _supported_os Linux # We need /dev/urandom
54
55 # $1: Export ID
56 # $2: Options (beyond the node-name and ID)
57 # $3: Expected return value (defaults to 'return')
58 # $4: Node to export (defaults to 'node-format')
59 fuse_export_add()
60 {
61     # The grep -v is a filter for errors when /etc/fuse.conf does not contain
62     # user_allow_other.  (The error is benign, but it is printed by fusermount
63     # on the first mount attempt, so our export code cannot hide it.)
64     _send_qemu_cmd $QEMU_HANDLE \
65         "{'execute': 'block-export-add',
66           'arguments': {
67               'type': 'fuse',
68               'id': '$1',
69               'node-name': '${4:-node-format}',
70               $2
71           } }" \
72         "${3:-return}" \
73         | _filter_imgfmt \
74         | grep -v 'option allow_other only allowed if'
75 }
76
77 # $1: Export ID
78 fuse_export_del()
79 {
80     _send_qemu_cmd $QEMU_HANDLE \
81         "{'execute': 'block-export-del',
82           'arguments': {
83               'id': '$1'
84           } }" \
85         'return'
86
87     _send_qemu_cmd $QEMU_HANDLE \
88         '' \
89         'BLOCK_EXPORT_DELETED'
90 }
91
92 # Return the length of the protocol file
93 # $1: Protocol node export mount point
94 # $2: Original file (to compare)
95 get_proto_len()
96 {
97     len1=$(stat -c '%s' "$1")
98     len2=$(stat -c '%s' "$2")
99
100     if [ "$len1" != "$len2" ]; then
101         echo 'ERROR: Length of export and original differ:' >&2
102         echo "$len1 != $len2" >&2
103     else
104         echo '(OK: Lengths of export and original are the same)' >&2
105     fi
106
107     echo "$len1"
108 }
109
110 COPIED_IMG="$TEST_IMG.copy"
111 EXT_MP="$TEST_IMG.fuse"
112
113 echo '=== Set up ==='
114
115 # Create image with random data
116 _make_test_img 64M
117 $QEMU_IO -c 'write -s /dev/urandom 0 64M' "$TEST_IMG" | _filter_qemu_io
118
119 _launch_qemu
120 _send_qemu_cmd $QEMU_HANDLE \
121     "{'execute': 'qmp_capabilities'}" \
122     'return'
123
124 # Separate blockdev-add calls for format and protocol so we can remove
125 # the format layer later on
126 _send_qemu_cmd $QEMU_HANDLE \
127     "{'execute': 'blockdev-add',
128       'arguments': {
129           'driver': 'file',
130           'node-name': 'node-protocol',
131           'filename': '$TEST_IMG'
132       } }" \
133     'return'
134
135 _send_qemu_cmd $QEMU_HANDLE \
136     "{'execute': 'blockdev-add',
137       'arguments': {
138           'driver': '$IMGFMT',
139           'node-name': 'node-format',
140           'file': 'node-protocol'
141       } }" \
142     'return'
143
144 echo
145 echo '=== Mountpoint not present ==='
146
147 rmdir "$EXT_MP" 2>/dev/null
148 rm -f "$EXT_MP"
149 output=$(fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error)
150
151 if echo "$output" | grep -q "Parameter 'type' does not accept value 'fuse'"; then
152     _notrun 'No FUSE support'
153 fi
154
155 echo "$output"
156
157 echo
158 echo '=== Mountpoint is a directory ==='
159
160 mkdir "$EXT_MP"
161 fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
162 rmdir "$EXT_MP"
163
164 echo
165 echo '=== Mountpoint is a regular file ==='
166
167 touch "$EXT_MP"
168 fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'"
169
170 # Check that the export presents the same data as the original image
171 $QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG"
172
173 # Some quick chmod tests
174 stat -c 'Permissions pre-chmod: %a' "$EXT_MP"
175
176 # Verify that we cannot set +w
177 chmod u+w "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
178 stat -c 'Permissions post-+w: %a' "$EXT_MP"
179
180 # But that we can set, say, +x (if we are so inclined)
181 chmod u+x "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
182 stat -c 'Permissions post-+x: %a' "$EXT_MP"
183
184 echo
185 echo '=== Mount over existing file ==='
186
187 # This is the coolest feature of FUSE exports: You can transparently
188 # make images in any format appear as raw images
189 fuse_export_add 'export-img' "'mountpoint': '$TEST_IMG'"
190
191 # Accesses both exports at the same time, so we get a concurrency test
192 $QEMU_IMG compare -f raw -F raw -U "$EXT_MP" "$TEST_IMG"
193
194 # Just to be sure, we later want to compare the data offline.  Also,
195 # this allows us to see that cp works without complaining.
196 # (This is not a given, because cp will expect a short read at EOF.
197 # Internally, qemu does not allow short reads, so we have to check
198 # whether the FUSE export driver lets them work.)
199 cp "$TEST_IMG" "$COPIED_IMG"
200
201 # $TEST_IMG will be in mode 0400 because it is read-only; we are going
202 # to write to the copy, so make it writable
203 chmod 0600 "$COPIED_IMG"
204
205 echo
206 echo '=== Double export ==='
207
208 # We have already seen that exporting a node twice works fine, but you
209 # cannot export anything twice on the same mount point.  The reason is
210 # that qemu has to stat the given mount point, and this would have to
211 # be answered by the same qemu instance if it already has an export
212 # there.  However, it cannot answer the stat because it is itself
213 # caught up in that same stat.
214 fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
215
216 echo
217 echo '=== Remove export ==='
218
219 # Double-check that $EXT_MP appears as a non-empty file (the raw image)
220 $QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size' | head -n 1
221
222 fuse_export_del 'export-mp'
223
224 # See that the file appears empty again
225 $QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size' | head -n 1
226
227 echo
228 echo '=== Writable export ==='
229
230 fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
231
232 # Check that writing to the read-only export fails
233 output=$($QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \
234              | _filter_qemu_io | _filter_testdir | _filter_imgfmt)
235
236 # Expected reference output: Opening the file fails because it has no
237 # write permission
238 reference="Could not open 'TEST_DIR/t.IMGFMT': Permission denied"
239
240 if echo "$output" | grep -q "$reference"; then
241     echo "Writing to read-only export failed: OK"
242 elif echo "$output" | grep -q "write failed: Permission denied"; then
243     # With CAP_DAC_OVERRIDE (e.g. when running this test as root), the export
244     # can be opened regardless of its file permissions, but writing will then
245     # fail.  This is not the result for which we want to test, so count this as
246     # a SKIP.
247     _casenotrun "Opening RO export as R/W succeeded, perhaps because of" \
248         "CAP_DAC_OVERRIDE"
249
250     # Still, write this to the reference output to make the test pass
251     echo "Writing to read-only export failed: OK"
252 else
253     echo "Writing to read-only export failed: ERROR"
254     echo "$output"
255 fi
256
257 # But here it should work
258 $QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
259
260 # (Adjust the copy, too)
261 $QEMU_IO -f raw -c 'write -P 42 1M 64k' "$COPIED_IMG" | _filter_qemu_io
262
263 echo
264 echo '=== Resizing exports ==='
265
266 # Here, we need to export the protocol node -- the format layer may
267 # not be growable, simply because the format does not support it.
268
269 # Remove all exports and the format node first so permissions will not
270 # get in the way
271 fuse_export_del 'export-mp'
272 fuse_export_del 'export-img'
273
274 _send_qemu_cmd $QEMU_HANDLE \
275     "{'execute': 'blockdev-del',
276       'arguments': {
277           'node-name': 'node-format'
278       } }" \
279     'return'
280
281 # Now export the protocol node
282 fuse_export_add \
283     'export-mp' \
284     "'mountpoint': '$EXT_MP', 'writable': true" \
285     'return' \
286     'node-protocol'
287
288 echo
289 echo '--- Try growing non-growable export ---'
290
291 # Get the current size so we can write beyond the EOF
292 orig_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
293 orig_disk_usage=$(stat -c '%b' "$TEST_IMG")
294
295 # Should fail (exports are non-growable by default)
296 # (Note that qemu-io can never write beyond the EOF, so we have to use
297 # dd here)
298 dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$orig_len 2>&1 \
299     | _filter_testdir | _filter_imgfmt
300
301 echo
302 echo '--- Resize export ---'
303
304 # But we can truncate it explicitly; even with fallocate
305 fallocate -o "$orig_len" -l 64k "$EXT_MP"
306
307 new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
308 if [ "$new_len" != "$((orig_len + 65536))" ]; then
309     echo 'ERROR: Unexpected post-truncate image size:'
310     echo "$new_len != $((orig_len + 65536))"
311 else
312     echo 'OK: Post-truncate image size is as expected'
313 fi
314
315 new_disk_usage=$(stat -c '%b' "$TEST_IMG")
316 if [ "$new_disk_usage" -gt "$orig_disk_usage" ]; then
317     echo 'OK: Disk usage grew with fallocate'
318 else
319     echo 'ERROR: Disk usage did not grow despite fallocate:'
320     echo "$orig_disk_usage => $new_disk_usage"
321 fi
322
323 echo
324 echo '--- Try growing growable export ---'
325
326 # Now export as growable
327 fuse_export_del 'export-mp'
328 fuse_export_add \
329     'export-mp' \
330     "'mountpoint': '$EXT_MP', 'writable': true, 'growable': true" \
331     'return' \
332     'node-protocol'
333
334 # Now we should be able to write beyond the EOF
335 dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$new_len 2>&1 \
336     | _filter_testdir | _filter_imgfmt
337
338 new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
339 if [ "$new_len" != "$((orig_len + 131072))" ]; then
340     echo 'ERROR: Unexpected post-grow image size:'
341     echo "$new_len != $((orig_len + 131072))"
342 else
343     echo 'OK: Post-grow image size is as expected'
344 fi
345
346 echo
347 echo '--- Shrink export ---'
348
349 # Now go back to the original size
350 truncate -s "$orig_len" "$EXT_MP"
351
352 new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
353 if [ "$new_len" != "$orig_len" ]; then
354     echo 'ERROR: Unexpected post-truncate image size:'
355     echo "$new_len != $orig_len"
356 else
357     echo 'OK: Post-truncate image size is as expected'
358 fi
359
360 echo
361 echo '=== Tear down ==='
362
363 _send_qemu_cmd $QEMU_HANDLE \
364     "{'execute': 'quit'}" \
365     'return'
366
367 wait=yes _cleanup_qemu
368
369 echo
370 echo '=== Compare copy with original ==='
371
372 $QEMU_IMG compare -f raw -F $IMGFMT "$COPIED_IMG" "$TEST_IMG"
373 _cleanup_test_img
374
375 echo
376 echo '=== Writing zeroes while unmapping ==='
377 # Regression test for https://gitlab.com/qemu-project/qemu/-/issues/1507
378 _make_test_img 64M
379 $QEMU_IO -c 'write -s /dev/urandom 0 64M' "$TEST_IMG" | _filter_qemu_io
380
381 _launch_qemu
382 _send_qemu_cmd $QEMU_HANDLE \
383     "{'execute': 'qmp_capabilities'}" \
384     'return'
385
386 _send_qemu_cmd $QEMU_HANDLE \
387     "{'execute': 'blockdev-add',
388       'arguments': {
389           'driver': '$IMGFMT',
390           'node-name': 'node-format',
391           'file': {
392               'driver': 'file',
393               'filename': '$TEST_IMG'
394           }
395       } }" \
396     'return'
397
398 fuse_export_add 'export' "'mountpoint': '$EXT_MP', 'writable': true"
399
400 # Try writing zeroes by unmapping
401 $QEMU_IO -f raw -c 'write -zu 0 64M' "$EXT_MP" | _filter_qemu_io
402
403 # Check the result
404 $QEMU_IO -f raw -c 'read -P 0 0 64M' "$EXT_MP" | _filter_qemu_io
405
406 _send_qemu_cmd $QEMU_HANDLE \
407     "{'execute': 'quit'}" \
408     'return'
409
410 wait=yes _cleanup_qemu
411
412 # Check the original image
413 $QEMU_IO -c 'read -P 0 0 64M' "$TEST_IMG" | _filter_qemu_io
414
415 _cleanup_test_img
416
417 # success, all done
418 echo "*** done"
419 rm -f $seq.full
420 status=0