xref: /openbmc/qemu/tests/qemu-iotests/308 (revision 8fc54f94)
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    # 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
78fuse_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)
95get_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
110COPIED_IMG="$TEST_IMG.copy"
111EXT_MP="$TEST_IMG.fuse"
112
113echo '=== 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
144echo
145echo '=== Mountpoint not present ==='
146
147rmdir "$EXT_MP" 2>/dev/null
148rm -f "$EXT_MP"
149output=$(fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error)
150
151if echo "$output" | grep -q "Invalid parameter 'fuse'"; then
152    _notrun 'No FUSE support'
153fi
154
155echo "$output"
156
157echo
158echo '=== Mountpoint is a directory ==='
159
160mkdir "$EXT_MP"
161fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
162rmdir "$EXT_MP"
163
164echo
165echo '=== Mountpoint is a regular file ==='
166
167touch "$EXT_MP"
168fuse_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
173echo
174echo '=== Mount over existing file ==='
175
176# This is the coolest feature of FUSE exports: You can transparently
177# make images in any format appear as raw images
178fuse_export_add 'export-img' "'mountpoint': '$TEST_IMG'"
179
180# Accesses both exports at the same time, so we get a concurrency test
181$QEMU_IMG compare -f raw -F raw -U "$EXT_MP" "$TEST_IMG"
182
183# Just to be sure, we later want to compare the data offline.  Also,
184# this allows us to see that cp works without complaining.
185# (This is not a given, because cp will expect a short read at EOF.
186# Internally, qemu does not allow short reads, so we have to check
187# whether the FUSE export driver lets them work.)
188cp "$TEST_IMG" "$COPIED_IMG"
189
190# $TEST_IMG will be in mode 0400 because it is read-only; we are going
191# to write to the copy, so make it writable
192chmod 0600 "$COPIED_IMG"
193
194echo
195echo '=== Double export ==='
196
197# We have already seen that exporting a node twice works fine, but you
198# cannot export anything twice on the same mount point.  The reason is
199# that qemu has to stat the given mount point, and this would have to
200# be answered by the same qemu instance if it already has an export
201# there.  However, it cannot answer the stat because it is itself
202# caught up in that same stat.
203fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
204
205echo
206echo '=== Remove export ==='
207
208# Double-check that $EXT_MP appears as a non-empty file (the raw image)
209$QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size'
210
211fuse_export_del 'export-mp'
212
213# See that the file appears empty again
214$QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size'
215
216echo
217echo '=== Writable export ==='
218
219fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
220
221# Check that writing to the read-only export fails
222$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \
223    | _filter_qemu_io | _filter_testdir | _filter_imgfmt
224
225# But here it should work
226$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
227
228# (Adjust the copy, too)
229$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$COPIED_IMG" | _filter_qemu_io
230
231echo
232echo '=== Resizing exports ==='
233
234# Here, we need to export the protocol node -- the format layer may
235# not be growable, simply because the format does not support it.
236
237# Remove all exports and the format node first so permissions will not
238# get in the way
239fuse_export_del 'export-mp'
240fuse_export_del 'export-img'
241
242_send_qemu_cmd $QEMU_HANDLE \
243    "{'execute': 'blockdev-del',
244      'arguments': {
245          'node-name': 'node-format'
246      } }" \
247    'return'
248
249# Now export the protocol node
250fuse_export_add \
251    'export-mp' \
252    "'mountpoint': '$EXT_MP', 'writable': true" \
253    'return' \
254    'node-protocol'
255
256echo
257echo '--- Try growing non-growable export ---'
258
259# Get the current size so we can write beyond the EOF
260orig_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
261orig_disk_usage=$(stat -c '%b' "$TEST_IMG")
262
263# Should fail (exports are non-growable by default)
264# (Note that qemu-io can never write beyond the EOF, so we have to use
265# dd here)
266dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$orig_len 2>&1 \
267    | _filter_testdir | _filter_imgfmt
268
269echo
270echo '--- Resize export ---'
271
272# But we can truncate it explicitly; even with fallocate
273fallocate -o "$orig_len" -l 64k "$EXT_MP"
274
275new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
276if [ "$new_len" != "$((orig_len + 65536))" ]; then
277    echo 'ERROR: Unexpected post-truncate image size:'
278    echo "$new_len != $((orig_len + 65536))"
279else
280    echo 'OK: Post-truncate image size is as expected'
281fi
282
283new_disk_usage=$(stat -c '%b' "$TEST_IMG")
284if [ "$new_disk_usage" -gt "$orig_disk_usage" ]; then
285    echo 'OK: Disk usage grew with fallocate'
286else
287    echo 'ERROR: Disk usage did not grow despite fallocate:'
288    echo "$orig_disk_usage => $new_disk_usage"
289fi
290
291echo
292echo '--- Try growing growable export ---'
293
294# Now export as growable
295fuse_export_del 'export-mp'
296fuse_export_add \
297    'export-mp' \
298    "'mountpoint': '$EXT_MP', 'writable': true, 'growable': true" \
299    'return' \
300    'node-protocol'
301
302# Now we should be able to write beyond the EOF
303dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$new_len 2>&1 \
304    | _filter_testdir | _filter_imgfmt
305
306new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
307if [ "$new_len" != "$((orig_len + 131072))" ]; then
308    echo 'ERROR: Unexpected post-grow image size:'
309    echo "$new_len != $((orig_len + 131072))"
310else
311    echo 'OK: Post-grow image size is as expected'
312fi
313
314echo
315echo '--- Shrink export ---'
316
317# Now go back to the original size
318truncate -s "$orig_len" "$EXT_MP"
319
320new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
321if [ "$new_len" != "$orig_len" ]; then
322    echo 'ERROR: Unexpected post-truncate image size:'
323    echo "$new_len != $orig_len"
324else
325    echo 'OK: Post-truncate image size is as expected'
326fi
327
328echo
329echo '=== Tear down ==='
330
331_send_qemu_cmd $QEMU_HANDLE \
332    "{'execute': 'quit'}" \
333    'return'
334
335wait=yes _cleanup_qemu
336
337echo
338echo '=== Compare copy with original ==='
339
340$QEMU_IMG compare -f raw -F $IMGFMT "$COPIED_IMG" "$TEST_IMG"
341
342# success, all done
343echo "*** done"
344rm -f $seq.full
345status=0
346