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