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