qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

308 (10819B)


      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 
     23 seq=$(basename "$0")
     24 echo "QA output created by $seq"
     25 
     26 status=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 }
     36 trap "_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
     45 if [ "$IMGOPTSSYNTAX" = "true" ]; then
     46     _unsupported_fmt $IMGFMT
     47 fi
     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')
     59 fuse_export_add()
     60 {
     61     # The grep -v is a filter for errors when /etc/fuse.conf does not contain
     62     # user_allow_other.  (The error is benign, but it is printed by fusermount
     63     # on the first mount attempt, so our export code cannot hide it.)
     64     _send_qemu_cmd $QEMU_HANDLE \
     65         "{'execute': 'block-export-add',
     66           'arguments': {
     67               'type': 'fuse',
     68               'id': '$1',
     69               'node-name': '${4:-node-format}',
     70               $2
     71           } }" \
     72         "${3:-return}" \
     73         | _filter_imgfmt \
     74         | grep -v 'option allow_other only allowed if'
     75 }
     76 
     77 # $1: Export ID
     78 fuse_export_del()
     79 {
     80     _send_qemu_cmd $QEMU_HANDLE \
     81         "{'execute': 'block-export-del',
     82           'arguments': {
     83               'id': '$1'
     84           } }" \
     85         'return'
     86 
     87     _send_qemu_cmd $QEMU_HANDLE \
     88         '' \
     89         'BLOCK_EXPORT_DELETED'
     90 }
     91 
     92 # Return the length of the protocol file
     93 # $1: Protocol node export mount point
     94 # $2: Original file (to compare)
     95 get_proto_len()
     96 {
     97     len1=$(stat -c '%s' "$1")
     98     len2=$(stat -c '%s' "$2")
     99 
    100     if [ "$len1" != "$len2" ]; then
    101         echo 'ERROR: Length of export and original differ:' >&2
    102         echo "$len1 != $len2" >&2
    103     else
    104         echo '(OK: Lengths of export and original are the same)' >&2
    105     fi
    106 
    107     echo "$len1"
    108 }
    109 
    110 COPIED_IMG="$TEST_IMG.copy"
    111 EXT_MP="$TEST_IMG.fuse"
    112 
    113 echo '=== Set up ==='
    114 
    115 # Create image with random data
    116 _make_test_img 64M
    117 $QEMU_IO -c 'write -s /dev/urandom 0 64M' "$TEST_IMG" | _filter_qemu_io
    118 
    119 _launch_qemu
    120 _send_qemu_cmd $QEMU_HANDLE \
    121     "{'execute': 'qmp_capabilities'}" \
    122     'return'
    123 
    124 # Separate blockdev-add calls for format and protocol so we can remove
    125 # the format layer later on
    126 _send_qemu_cmd $QEMU_HANDLE \
    127     "{'execute': 'blockdev-add',
    128       'arguments': {
    129           'driver': 'file',
    130           'node-name': 'node-protocol',
    131           'filename': '$TEST_IMG'
    132       } }" \
    133     'return'
    134 
    135 _send_qemu_cmd $QEMU_HANDLE \
    136     "{'execute': 'blockdev-add',
    137       'arguments': {
    138           'driver': '$IMGFMT',
    139           'node-name': 'node-format',
    140           'file': 'node-protocol'
    141       } }" \
    142     'return'
    143 
    144 echo
    145 echo '=== Mountpoint not present ==='
    146 
    147 rmdir "$EXT_MP" 2>/dev/null
    148 rm -f "$EXT_MP"
    149 output=$(fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error)
    150 
    151 if echo "$output" | grep -q "Parameter 'type' does not accept value 'fuse'"; then
    152     _notrun 'No FUSE support'
    153 fi
    154 
    155 echo "$output"
    156 
    157 echo
    158 echo '=== Mountpoint is a directory ==='
    159 
    160 mkdir "$EXT_MP"
    161 fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
    162 rmdir "$EXT_MP"
    163 
    164 echo
    165 echo '=== Mountpoint is a regular file ==='
    166 
    167 touch "$EXT_MP"
    168 fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'"
    169 
    170 # Check that the export presents the same data as the original image
    171 $QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG"
    172 
    173 # Some quick chmod tests
    174 stat -c 'Permissions pre-chmod: %a' "$EXT_MP"
    175 
    176 # Verify that we cannot set +w
    177 chmod u+w "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
    178 stat -c 'Permissions post-+w: %a' "$EXT_MP"
    179 
    180 # But that we can set, say, +x (if we are so inclined)
    181 chmod u+x "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt
    182 stat -c 'Permissions post-+x: %a' "$EXT_MP"
    183 
    184 echo
    185 echo '=== Mount over existing file ==='
    186 
    187 # This is the coolest feature of FUSE exports: You can transparently
    188 # make images in any format appear as raw images
    189 fuse_export_add 'export-img' "'mountpoint': '$TEST_IMG'"
    190 
    191 # Accesses both exports at the same time, so we get a concurrency test
    192 $QEMU_IMG compare -f raw -F raw -U "$EXT_MP" "$TEST_IMG"
    193 
    194 # Just to be sure, we later want to compare the data offline.  Also,
    195 # this allows us to see that cp works without complaining.
    196 # (This is not a given, because cp will expect a short read at EOF.
    197 # Internally, qemu does not allow short reads, so we have to check
    198 # whether the FUSE export driver lets them work.)
    199 cp "$TEST_IMG" "$COPIED_IMG"
    200 
    201 # $TEST_IMG will be in mode 0400 because it is read-only; we are going
    202 # to write to the copy, so make it writable
    203 chmod 0600 "$COPIED_IMG"
    204 
    205 echo
    206 echo '=== Double export ==='
    207 
    208 # We have already seen that exporting a node twice works fine, but you
    209 # cannot export anything twice on the same mount point.  The reason is
    210 # that qemu has to stat the given mount point, and this would have to
    211 # be answered by the same qemu instance if it already has an export
    212 # there.  However, it cannot answer the stat because it is itself
    213 # caught up in that same stat.
    214 fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error
    215 
    216 echo
    217 echo '=== Remove export ==='
    218 
    219 # Double-check that $EXT_MP appears as a non-empty file (the raw image)
    220 $QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size'
    221 
    222 fuse_export_del 'export-mp'
    223 
    224 # See that the file appears empty again
    225 $QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size'
    226 
    227 echo
    228 echo '=== Writable export ==='
    229 
    230 fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true"
    231 
    232 # Check that writing to the read-only export fails
    233 output=$($QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \
    234              | _filter_qemu_io | _filter_testdir | _filter_imgfmt)
    235 
    236 # Expected reference output: Opening the file fails because it has no
    237 # write permission
    238 reference="Could not open 'TEST_DIR/t.IMGFMT': Permission denied"
    239 
    240 if echo "$output" | grep -q "$reference"; then
    241     echo "Writing to read-only export failed: OK"
    242 elif echo "$output" | grep -q "write failed: Permission denied"; then
    243     # With CAP_DAC_OVERRIDE (e.g. when running this test as root), the export
    244     # can be opened regardless of its file permissions, but writing will then
    245     # fail.  This is not the result for which we want to test, so count this as
    246     # a SKIP.
    247     _casenotrun "Opening RO export as R/W succeeded, perhaps because of" \
    248         "CAP_DAC_OVERRIDE"
    249 
    250     # Still, write this to the reference output to make the test pass
    251     echo "Writing to read-only export failed: OK"
    252 else
    253     echo "Writing to read-only export failed: ERROR"
    254     echo "$output"
    255 fi
    256 
    257 # But here it should work
    258 $QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io
    259 
    260 # (Adjust the copy, too)
    261 $QEMU_IO -f raw -c 'write -P 42 1M 64k' "$COPIED_IMG" | _filter_qemu_io
    262 
    263 echo
    264 echo '=== Resizing exports ==='
    265 
    266 # Here, we need to export the protocol node -- the format layer may
    267 # not be growable, simply because the format does not support it.
    268 
    269 # Remove all exports and the format node first so permissions will not
    270 # get in the way
    271 fuse_export_del 'export-mp'
    272 fuse_export_del 'export-img'
    273 
    274 _send_qemu_cmd $QEMU_HANDLE \
    275     "{'execute': 'blockdev-del',
    276       'arguments': {
    277           'node-name': 'node-format'
    278       } }" \
    279     'return'
    280 
    281 # Now export the protocol node
    282 fuse_export_add \
    283     'export-mp' \
    284     "'mountpoint': '$EXT_MP', 'writable': true" \
    285     'return' \
    286     'node-protocol'
    287 
    288 echo
    289 echo '--- Try growing non-growable export ---'
    290 
    291 # Get the current size so we can write beyond the EOF
    292 orig_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
    293 orig_disk_usage=$(stat -c '%b' "$TEST_IMG")
    294 
    295 # Should fail (exports are non-growable by default)
    296 # (Note that qemu-io can never write beyond the EOF, so we have to use
    297 # dd here)
    298 dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$orig_len 2>&1 \
    299     | _filter_testdir | _filter_imgfmt
    300 
    301 echo
    302 echo '--- Resize export ---'
    303 
    304 # But we can truncate it explicitly; even with fallocate
    305 fallocate -o "$orig_len" -l 64k "$EXT_MP"
    306 
    307 new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
    308 if [ "$new_len" != "$((orig_len + 65536))" ]; then
    309     echo 'ERROR: Unexpected post-truncate image size:'
    310     echo "$new_len != $((orig_len + 65536))"
    311 else
    312     echo 'OK: Post-truncate image size is as expected'
    313 fi
    314 
    315 new_disk_usage=$(stat -c '%b' "$TEST_IMG")
    316 if [ "$new_disk_usage" -gt "$orig_disk_usage" ]; then
    317     echo 'OK: Disk usage grew with fallocate'
    318 else
    319     echo 'ERROR: Disk usage did not grow despite fallocate:'
    320     echo "$orig_disk_usage => $new_disk_usage"
    321 fi
    322 
    323 echo
    324 echo '--- Try growing growable export ---'
    325 
    326 # Now export as growable
    327 fuse_export_del 'export-mp'
    328 fuse_export_add \
    329     'export-mp' \
    330     "'mountpoint': '$EXT_MP', 'writable': true, 'growable': true" \
    331     'return' \
    332     'node-protocol'
    333 
    334 # Now we should be able to write beyond the EOF
    335 dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$new_len 2>&1 \
    336     | _filter_testdir | _filter_imgfmt
    337 
    338 new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
    339 if [ "$new_len" != "$((orig_len + 131072))" ]; then
    340     echo 'ERROR: Unexpected post-grow image size:'
    341     echo "$new_len != $((orig_len + 131072))"
    342 else
    343     echo 'OK: Post-grow image size is as expected'
    344 fi
    345 
    346 echo
    347 echo '--- Shrink export ---'
    348 
    349 # Now go back to the original size
    350 truncate -s "$orig_len" "$EXT_MP"
    351 
    352 new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG")
    353 if [ "$new_len" != "$orig_len" ]; then
    354     echo 'ERROR: Unexpected post-truncate image size:'
    355     echo "$new_len != $orig_len"
    356 else
    357     echo 'OK: Post-truncate image size is as expected'
    358 fi
    359 
    360 echo
    361 echo '=== Tear down ==='
    362 
    363 _send_qemu_cmd $QEMU_HANDLE \
    364     "{'execute': 'quit'}" \
    365     'return'
    366 
    367 wait=yes _cleanup_qemu
    368 
    369 echo
    370 echo '=== Compare copy with original ==='
    371 
    372 $QEMU_IMG compare -f raw -F $IMGFMT "$COPIED_IMG" "$TEST_IMG"
    373 
    374 # success, all done
    375 echo "*** done"
    376 rm -f $seq.full
    377 status=0