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