74163adda3
These tests read size information (sometimes disk size, sometimes virtual size) from qemu-img info's output. Once qemu-img starts printing info about child nodes, we are going to see multiple instances of that per image, but these tests are only interested in the first one, so use "head -n 1" to get it. Signed-off-by: Hanna Reitz <hreitz@redhat.com> Message-Id: <20220620162704.80987-11-hreitz@redhat.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
378 lines
11 KiB
Bash
Executable File
378 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# group: rw
|
|
#
|
|
# Test FUSE exports (in ways that are not captured by the generic
|
|
# tests)
|
|
#
|
|
# Copyright (C) 2020 Red Hat, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
seq=$(basename "$0")
|
|
echo "QA output created by $seq"
|
|
|
|
status=1 # failure is the default!
|
|
|
|
_cleanup()
|
|
{
|
|
_cleanup_qemu
|
|
_cleanup_test_img
|
|
rmdir "$EXT_MP" 2>/dev/null
|
|
rm -f "$EXT_MP"
|
|
rm -f "$COPIED_IMG"
|
|
}
|
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
|
|
|
# get standard environment, filters and checks
|
|
. ./common.rc
|
|
. ./common.filter
|
|
. ./common.qemu
|
|
|
|
# Generic format, but needs a plain filename
|
|
_supported_fmt generic
|
|
if [ "$IMGOPTSSYNTAX" = "true" ]; then
|
|
_unsupported_fmt $IMGFMT
|
|
fi
|
|
# We need the image to have exactly the specified size, and VPC does
|
|
# not allow that by default
|
|
_unsupported_fmt vpc
|
|
|
|
_supported_proto file # We create the FUSE export manually
|
|
_supported_os Linux # We need /dev/urandom
|
|
|
|
# $1: Export ID
|
|
# $2: Options (beyond the node-name and ID)
|
|
# $3: Expected return value (defaults to 'return')
|
|
# $4: Node to export (defaults to 'node-format')
|
|
fuse_export_add()
|
|
{
|
|
# The grep -v is a filter for errors when /etc/fuse.conf does not contain
|
|
# user_allow_other. (The error is benign, but it is printed by fusermount
|
|
# on the first mount attempt, so our export code cannot hide it.)
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'block-export-add',
|
|
'arguments': {
|
|
'type': 'fuse',
|
|
'id': '$1',
|
|
'node-name': '${4:-node-format}',
|
|
$2
|
|
} }" \
|
|
"${3:-return}" \
|
|
| _filter_imgfmt \
|
|
| grep -v 'option allow_other only allowed if'
|
|
}
|
|
|
|
# $1: Export ID
|
|
fuse_export_del()
|
|
{
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'block-export-del',
|
|
'arguments': {
|
|
'id': '$1'
|
|
} }" \
|
|
'return'
|
|
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
'' \
|
|
'BLOCK_EXPORT_DELETED'
|
|
}
|
|
|
|
# Return the length of the protocol file
|
|
# $1: Protocol node export mount point
|
|
# $2: Original file (to compare)
|
|
get_proto_len()
|
|
{
|
|
len1=$(stat -c '%s' "$1")
|
|
len2=$(stat -c '%s' "$2")
|
|
|
|
if [ "$len1" != "$len2" ]; then
|
|
echo 'ERROR: Length of export and original differ:' >&2
|
|
echo "$len1 != $len2" >&2
|
|
else
|
|
echo '(OK: Lengths of export and original are the same)' >&2
|
|
fi
|
|
|
|
echo "$len1"
|
|
}
|
|
|
|
COPIED_IMG="$TEST_IMG.copy"
|
|
EXT_MP="$TEST_IMG.fuse"
|
|
|
|
echo '=== Set up ==='
|
|
|
|
# Create image with random data
|
|
_make_test_img 64M
|
|
$QEMU_IO -c 'write -s /dev/urandom 0 64M' "$TEST_IMG" | _filter_qemu_io
|
|
|
|
_launch_qemu
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'qmp_capabilities'}" \
|
|
'return'
|
|
|
|
# Separate blockdev-add calls for format and protocol so we can remove
|
|
# the format layer later on
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'blockdev-add',
|
|
'arguments': {
|
|
'driver': 'file',
|
|
'node-name': 'node-protocol',
|
|
'filename': '$TEST_IMG'
|
|
} }" \
|
|
'return'
|
|
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'blockdev-add',
|
|
'arguments': {
|
|
'driver': '$IMGFMT',
|
|
'node-name': 'node-format',
|
|
'file': 'node-protocol'
|
|
} }" \
|
|
'return'
|
|
|
|
echo
|
|
echo '=== Mountpoint not present ==='
|
|
|
|
rmdir "$EXT_MP" 2>/dev/null
|
|
rm -f "$EXT_MP"
|
|
output=$(fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error)
|
|
|
|
if echo "$output" | grep -q "Parameter 'type' does not accept value 'fuse'"; then
|
|
_notrun 'No FUSE support'
|
|
fi
|
|
|
|
echo "$output"
|
|
|
|
echo
|
|
echo '=== Mountpoint is a directory ==='
|
|
|
|
mkdir "$EXT_MP"
|
|
fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
|
|
rmdir "$EXT_MP"
|
|
|
|
echo
|
|
echo '=== Mountpoint is a regular file ==='
|
|
|
|
touch "$EXT_MP"
|
|
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'"
|
|
|
|
# Check that the export presents the same data as the original image
|
|
$QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG"
|
|
|
|
# Some quick chmod tests
|
|
stat -c 'Permissions pre-chmod: %a' "$EXT_MP"
|
|
|
|
# Verify that we cannot set +w
|
|
chmod u+w "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
|
stat -c 'Permissions post-+w: %a' "$EXT_MP"
|
|
|
|
# But that we can set, say, +x (if we are so inclined)
|
|
chmod u+x "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
|
|
stat -c 'Permissions post-+x: %a' "$EXT_MP"
|
|
|
|
echo
|
|
echo '=== Mount over existing file ==='
|
|
|
|
# This is the coolest feature of FUSE exports: You can transparently
|
|
# make images in any format appear as raw images
|
|
fuse_export_add 'export-img' "'mountpoint': '$TEST_IMG'"
|
|
|
|
# Accesses both exports at the same time, so we get a concurrency test
|
|
$QEMU_IMG compare -f raw -F raw -U "$EXT_MP" "$TEST_IMG"
|
|
|
|
# Just to be sure, we later want to compare the data offline. Also,
|
|
# this allows us to see that cp works without complaining.
|
|
# (This is not a given, because cp will expect a short read at EOF.
|
|
# Internally, qemu does not allow short reads, so we have to check
|
|
# whether the FUSE export driver lets them work.)
|
|
cp "$TEST_IMG" "$COPIED_IMG"
|
|
|
|
# $TEST_IMG will be in mode 0400 because it is read-only; we are going
|
|
# to write to the copy, so make it writable
|
|
chmod 0600 "$COPIED_IMG"
|
|
|
|
echo
|
|
echo '=== Double export ==='
|
|
|
|
# We have already seen that exporting a node twice works fine, but you
|
|
# cannot export anything twice on the same mount point. The reason is
|
|
# that qemu has to stat the given mount point, and this would have to
|
|
# be answered by the same qemu instance if it already has an export
|
|
# there. However, it cannot answer the stat because it is itself
|
|
# caught up in that same stat.
|
|
fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
|
|
|
|
echo
|
|
echo '=== Remove export ==='
|
|
|
|
# Double-check that $EXT_MP appears as a non-empty file (the raw image)
|
|
$QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size' | head -n 1
|
|
|
|
fuse_export_del 'export-mp'
|
|
|
|
# See that the file appears empty again
|
|
$QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size' | head -n 1
|
|
|
|
echo
|
|
echo '=== Writable export ==='
|
|
|
|
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
|
|
|
|
# Check that writing to the read-only export fails
|
|
output=$($QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \
|
|
| _filter_qemu_io | _filter_testdir | _filter_imgfmt)
|
|
|
|
# Expected reference output: Opening the file fails because it has no
|
|
# write permission
|
|
reference="Could not open 'TEST_DIR/t.IMGFMT': Permission denied"
|
|
|
|
if echo "$output" | grep -q "$reference"; then
|
|
echo "Writing to read-only export failed: OK"
|
|
elif echo "$output" | grep -q "write failed: Permission denied"; then
|
|
# With CAP_DAC_OVERRIDE (e.g. when running this test as root), the export
|
|
# can be opened regardless of its file permissions, but writing will then
|
|
# fail. This is not the result for which we want to test, so count this as
|
|
# a SKIP.
|
|
_casenotrun "Opening RO export as R/W succeeded, perhaps because of" \
|
|
"CAP_DAC_OVERRIDE"
|
|
|
|
# Still, write this to the reference output to make the test pass
|
|
echo "Writing to read-only export failed: OK"
|
|
else
|
|
echo "Writing to read-only export failed: ERROR"
|
|
echo "$output"
|
|
fi
|
|
|
|
# But here it should work
|
|
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
|
|
|
|
# (Adjust the copy, too)
|
|
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$COPIED_IMG" | _filter_qemu_io
|
|
|
|
echo
|
|
echo '=== Resizing exports ==='
|
|
|
|
# Here, we need to export the protocol node -- the format layer may
|
|
# not be growable, simply because the format does not support it.
|
|
|
|
# Remove all exports and the format node first so permissions will not
|
|
# get in the way
|
|
fuse_export_del 'export-mp'
|
|
fuse_export_del 'export-img'
|
|
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'blockdev-del',
|
|
'arguments': {
|
|
'node-name': 'node-format'
|
|
} }" \
|
|
'return'
|
|
|
|
# Now export the protocol node
|
|
fuse_export_add \
|
|
'export-mp' \
|
|
"'mountpoint': '$EXT_MP', 'writable': true" \
|
|
'return' \
|
|
'node-protocol'
|
|
|
|
echo
|
|
echo '--- Try growing non-growable export ---'
|
|
|
|
# Get the current size so we can write beyond the EOF
|
|
orig_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
|
|
orig_disk_usage=$(stat -c '%b' "$TEST_IMG")
|
|
|
|
# Should fail (exports are non-growable by default)
|
|
# (Note that qemu-io can never write beyond the EOF, so we have to use
|
|
# dd here)
|
|
dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$orig_len 2>&1 \
|
|
| _filter_testdir | _filter_imgfmt
|
|
|
|
echo
|
|
echo '--- Resize export ---'
|
|
|
|
# But we can truncate it explicitly; even with fallocate
|
|
fallocate -o "$orig_len" -l 64k "$EXT_MP"
|
|
|
|
new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
|
|
if [ "$new_len" != "$((orig_len + 65536))" ]; then
|
|
echo 'ERROR: Unexpected post-truncate image size:'
|
|
echo "$new_len != $((orig_len + 65536))"
|
|
else
|
|
echo 'OK: Post-truncate image size is as expected'
|
|
fi
|
|
|
|
new_disk_usage=$(stat -c '%b' "$TEST_IMG")
|
|
if [ "$new_disk_usage" -gt "$orig_disk_usage" ]; then
|
|
echo 'OK: Disk usage grew with fallocate'
|
|
else
|
|
echo 'ERROR: Disk usage did not grow despite fallocate:'
|
|
echo "$orig_disk_usage => $new_disk_usage"
|
|
fi
|
|
|
|
echo
|
|
echo '--- Try growing growable export ---'
|
|
|
|
# Now export as growable
|
|
fuse_export_del 'export-mp'
|
|
fuse_export_add \
|
|
'export-mp' \
|
|
"'mountpoint': '$EXT_MP', 'writable': true, 'growable': true" \
|
|
'return' \
|
|
'node-protocol'
|
|
|
|
# Now we should be able to write beyond the EOF
|
|
dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$new_len 2>&1 \
|
|
| _filter_testdir | _filter_imgfmt
|
|
|
|
new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
|
|
if [ "$new_len" != "$((orig_len + 131072))" ]; then
|
|
echo 'ERROR: Unexpected post-grow image size:'
|
|
echo "$new_len != $((orig_len + 131072))"
|
|
else
|
|
echo 'OK: Post-grow image size is as expected'
|
|
fi
|
|
|
|
echo
|
|
echo '--- Shrink export ---'
|
|
|
|
# Now go back to the original size
|
|
truncate -s "$orig_len" "$EXT_MP"
|
|
|
|
new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
|
|
if [ "$new_len" != "$orig_len" ]; then
|
|
echo 'ERROR: Unexpected post-truncate image size:'
|
|
echo "$new_len != $orig_len"
|
|
else
|
|
echo 'OK: Post-truncate image size is as expected'
|
|
fi
|
|
|
|
echo
|
|
echo '=== Tear down ==='
|
|
|
|
_send_qemu_cmd $QEMU_HANDLE \
|
|
"{'execute': 'quit'}" \
|
|
'return'
|
|
|
|
wait=yes _cleanup_qemu
|
|
|
|
echo
|
|
echo '=== Compare copy with original ==='
|
|
|
|
$QEMU_IMG compare -f raw -F $IMGFMT "$COPIED_IMG" "$TEST_IMG"
|
|
|
|
# success, all done
|
|
echo "*** done"
|
|
rm -f $seq.full
|
|
status=0
|