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