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