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