qemu

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

261 (16776B)


      1 #!/usr/bin/env bash
      2 # group: rw
      3 #
      4 # Test case for qcow2's handling of extra data in snapshot table entries
      5 # (and more generally, how certain cases of broken snapshot tables are
      6 # handled)
      7 #
      8 # Copyright (C) 2019 Red Hat, Inc.
      9 #
     10 # This program is free software; you can redistribute it and/or modify
     11 # it under the terms of the GNU General Public License as published by
     12 # the Free Software Foundation; either version 2 of the License, or
     13 # (at your option) any later version.
     14 #
     15 # This program is distributed in the hope that it will be useful,
     16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18 # GNU General Public License for more details.
     19 #
     20 # You should have received a copy of the GNU General Public License
     21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     22 #
     23 
     24 # creator
     25 owner=hreitz@redhat.com
     26 
     27 seq=$(basename $0)
     28 echo "QA output created by $seq"
     29 
     30 status=1	# failure is the default!
     31 
     32 _cleanup()
     33 {
     34     _cleanup_test_img
     35     rm -f "$TEST_IMG".v{2,3}.orig
     36     rm -f "$TEST_DIR"/sn{0,1,2}{,-pre,-extra,-post}
     37 }
     38 trap "_cleanup; exit \$status" 0 1 2 3 15
     39 
     40 # get standard environment, filters and checks
     41 . ./common.rc
     42 . ./common.filter
     43 
     44 # This tests qcow2-specific low-level functionality
     45 _supported_fmt qcow2
     46 _supported_proto file
     47 _supported_os Linux
     48 # (1) We create a v2 image that supports nothing but refcount_bits=16
     49 # (2) We do some refcount management on our own which expects
     50 #     refcount_bits=16
     51 # As for data files, they do not support snapshots at all.
     52 _unsupported_imgopts 'refcount_bits=\([^1]\|.\([^6]\|$\)\)' data_file
     53 
     54 # Parameters:
     55 #   $1: image filename
     56 #   $2: snapshot table entry offset in the image
     57 snapshot_table_entry_size()
     58 {
     59     id_len=$(peek_file_be "$1" $(($2 + 12)) 2)
     60     name_len=$(peek_file_be "$1" $(($2 + 14)) 2)
     61     extra_len=$(peek_file_be "$1" $(($2 + 36)) 4)
     62 
     63     full_len=$((40 + extra_len + id_len + name_len))
     64     echo $(((full_len + 7) / 8 * 8))
     65 }
     66 
     67 # Parameter:
     68 #   $1: image filename
     69 print_snapshot_table()
     70 {
     71     nb_entries=$(peek_file_be "$1" 60 4)
     72     offset=$(peek_file_be "$1" 64 8)
     73 
     74     echo "Snapshots in $1:" | _filter_testdir | _filter_imgfmt
     75 
     76     for ((i = 0; i < nb_entries; i++)); do
     77         id_len=$(peek_file_be "$1" $((offset + 12)) 2)
     78         name_len=$(peek_file_be "$1" $((offset + 14)) 2)
     79         extra_len=$(peek_file_be "$1" $((offset + 36)) 4)
     80 
     81         extra_ofs=$((offset + 40))
     82         id_ofs=$((extra_ofs + extra_len))
     83         name_ofs=$((id_ofs + id_len))
     84 
     85         echo "  [$i]"
     86         echo "    ID: $(peek_file_raw "$1" $id_ofs $id_len)"
     87         echo "    Name: $(peek_file_raw "$1" $name_ofs $name_len)"
     88         echo "    Extra data size: $extra_len"
     89         if [ $extra_len -ge 8 ]; then
     90             echo "    VM state size: $(peek_file_be "$1" $extra_ofs 8)"
     91         fi
     92         if [ $extra_len -ge 16 ]; then
     93             echo "    Disk size: $(peek_file_be "$1" $((extra_ofs + 8)) 8)"
     94         fi
     95         if [ $extra_len -ge 24 ]; then
     96             echo "    Icount: $(peek_file_be "$1" $((extra_ofs + 16)) 8)"
     97         fi
     98         if [ $extra_len -gt 24 ]; then
     99             echo '    Unknown extra data:' \
    100                 "$(peek_file_raw "$1" $((extra_ofs + 16)) $((extra_len - 16)) \
    101                    | tr -d '\0')"
    102         fi
    103 
    104         offset=$((offset + $(snapshot_table_entry_size "$1" $offset)))
    105     done
    106 }
    107 
    108 # Mark clusters as allocated; works only in refblock 0 (i.e. before
    109 # cluster #32768).
    110 # Parameters:
    111 #   $1: Start offset of what to allocate
    112 #   $2: End offset (exclusive)
    113 refblock0_allocate()
    114 {
    115     reftable_ofs=$(peek_file_be "$TEST_IMG" 48 8)
    116     refblock_ofs=$(peek_file_be "$TEST_IMG" $reftable_ofs 8)
    117 
    118     cluster=$(($1 / 65536))
    119     ecluster=$((($2 + 65535) / 65536))
    120 
    121     while [ $cluster -lt $ecluster ]; do
    122         if [ $cluster -ge 32768 ]; then
    123             echo "*** Abort: Cluster $cluster exceeds refblock 0 ***"
    124             exit 1
    125         fi
    126         poke_file "$TEST_IMG" $((refblock_ofs + cluster * 2)) '\x00\x01'
    127         cluster=$((cluster + 1))
    128     done
    129 }
    130 
    131 
    132 echo
    133 echo '=== Create v2 template ==='
    134 echo
    135 
    136 # Create v2 image with a snapshot table with three entries:
    137 # [0]: No extra data (valid with v2, not valid with v3)
    138 # [1]: Has extra data unknown to qemu
    139 # [2]: Has the 64-bit VM state size, but not the disk size (again,
    140 #      valid with v2, not valid with v3)
    141 
    142 TEST_IMG="$TEST_IMG.v2.orig" IMGOPTS='compat=0.10' _make_test_img 64M
    143 $QEMU_IMG snapshot -c sn0 "$TEST_IMG.v2.orig"
    144 $QEMU_IMG snapshot -c sn1 "$TEST_IMG.v2.orig"
    145 $QEMU_IMG snapshot -c sn2 "$TEST_IMG.v2.orig"
    146 
    147 # Copy out all existing snapshot table entries
    148 sn_table_ofs=$(peek_file_be "$TEST_IMG.v2.orig" 64 8)
    149 
    150 # ofs: Snapshot table entry offset
    151 # eds: Extra data size
    152 # ids: Name + ID size
    153 # len: Total entry length
    154 sn0_ofs=$sn_table_ofs
    155 sn0_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 36)) 4)
    156 sn0_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 12)) 2) +
    157            $(peek_file_be "$TEST_IMG.v2.orig" $((sn0_ofs + 14)) 2)))
    158 sn0_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn0_ofs)
    159 sn1_ofs=$((sn0_ofs + sn0_len))
    160 sn1_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 36)) 4)
    161 sn1_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 12)) 2) +
    162            $(peek_file_be "$TEST_IMG.v2.orig" $((sn1_ofs + 14)) 2)))
    163 sn1_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn1_ofs)
    164 sn2_ofs=$((sn1_ofs + sn1_len))
    165 sn2_eds=$(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 36)) 4)
    166 sn2_ids=$(($(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 12)) 2) +
    167            $(peek_file_be "$TEST_IMG.v2.orig" $((sn2_ofs + 14)) 2)))
    168 sn2_len=$(snapshot_table_entry_size "$TEST_IMG.v2.orig" $sn2_ofs)
    169 
    170 # Data before extra data
    171 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-pre" bs=1 skip=$sn0_ofs count=40 \
    172     &> /dev/null
    173 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-pre" bs=1 skip=$sn1_ofs count=40 \
    174     &> /dev/null
    175 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-pre" bs=1 skip=$sn2_ofs count=40 \
    176     &> /dev/null
    177 
    178 # Extra data
    179 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-extra" bs=1 \
    180     skip=$((sn0_ofs + 40)) count=$sn0_eds &> /dev/null
    181 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-extra" bs=1 \
    182     skip=$((sn1_ofs + 40)) count=$sn1_eds &> /dev/null
    183 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-extra" bs=1 \
    184     skip=$((sn2_ofs + 40)) count=$sn2_eds &> /dev/null
    185 
    186 # Data after extra data
    187 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn0-post" bs=1 \
    188     skip=$((sn0_ofs + 40 + sn0_eds)) count=$sn0_ids \
    189     &> /dev/null
    190 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn1-post" bs=1 \
    191     skip=$((sn1_ofs + 40 + sn1_eds)) count=$sn1_ids \
    192     &> /dev/null
    193 dd if="$TEST_IMG.v2.orig" of="$TEST_DIR/sn2-post" bs=1 \
    194     skip=$((sn2_ofs + 40 + sn2_eds)) count=$sn2_ids \
    195     &> /dev/null
    196 
    197 # Amend them, one by one
    198 # Set sn0's extra data size to 0
    199 poke_file "$TEST_DIR/sn0-pre" 36 '\x00\x00\x00\x00'
    200 truncate -s 0 "$TEST_DIR/sn0-extra"
    201 # Grow sn0-post to pad
    202 truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn0-pre") - 40)) \
    203     "$TEST_DIR/sn0-post"
    204 
    205 # Set sn1's extra data size to 50
    206 poke_file "$TEST_DIR/sn1-pre" 36 '\x00\x00\x00\x32'
    207 truncate -s 50 "$TEST_DIR/sn1-extra"
    208 poke_file "$TEST_DIR/sn1-extra" 24 'very important data'
    209 # Grow sn1-post to pad
    210 truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn1-pre") - 90)) \
    211     "$TEST_DIR/sn1-post"
    212 
    213 # Set sn2's extra data size to 8
    214 poke_file "$TEST_DIR/sn2-pre" 36 '\x00\x00\x00\x08'
    215 truncate -s 8 "$TEST_DIR/sn2-extra"
    216 # Grow sn2-post to pad
    217 truncate -s $(($(snapshot_table_entry_size "$TEST_DIR/sn2-pre") - 48)) \
    218     "$TEST_DIR/sn2-post"
    219 
    220 # Construct snapshot table
    221 cat "$TEST_DIR"/sn0-{pre,extra,post} \
    222     "$TEST_DIR"/sn1-{pre,extra,post} \
    223     "$TEST_DIR"/sn2-{pre,extra,post} \
    224     | dd of="$TEST_IMG.v2.orig" bs=1 seek=$sn_table_ofs conv=notrunc \
    225           &> /dev/null
    226 
    227 # Done!
    228 TEST_IMG="$TEST_IMG.v2.orig" _check_test_img
    229 print_snapshot_table "$TEST_IMG.v2.orig"
    230 
    231 echo
    232 echo '=== Upgrade to v3 ==='
    233 echo
    234 
    235 cp "$TEST_IMG.v2.orig" "$TEST_IMG.v3.orig"
    236 $QEMU_IMG amend -o compat=1.1 "$TEST_IMG.v3.orig"
    237 TEST_IMG="$TEST_IMG.v3.orig" _check_test_img
    238 print_snapshot_table "$TEST_IMG.v3.orig"
    239 
    240 echo
    241 echo '=== Repair botched v3 ==='
    242 echo
    243 
    244 # Force the v2 file to be v3.  v3 requires each snapshot table entry
    245 # to have at least 16 bytes of extra data, so it will not comply to
    246 # the qcow2 v3 specification; but we can fix that.
    247 cp "$TEST_IMG.v2.orig" "$TEST_IMG"
    248 
    249 # Set version
    250 poke_file "$TEST_IMG" 4 '\x00\x00\x00\x03'
    251 # Increase header length (necessary for v3)
    252 poke_file "$TEST_IMG" 100 '\x00\x00\x00\x68'
    253 # Set refcount order (necessary for v3)
    254 poke_file "$TEST_IMG" 96 '\x00\x00\x00\x04'
    255 
    256 _check_test_img -r all
    257 print_snapshot_table "$TEST_IMG"
    258 
    259 
    260 # From now on, just test the qcow2 version we are supposed to test.
    261 # (v3 by default, v2 by choice through $IMGOPTS.)
    262 # That works because we always write all known extra data when
    263 # updating the snapshot table, independent of the version.
    264 
    265 if echo "$IMGOPTS" | grep -q 'compat=\(0\.10\|v2\)' 2> /dev/null; then
    266     subver=v2
    267 else
    268     subver=v3
    269 fi
    270 
    271 echo
    272 echo '=== Add new snapshot ==='
    273 echo
    274 
    275 cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
    276 $QEMU_IMG snapshot -c sn3 "$TEST_IMG"
    277 _check_test_img
    278 print_snapshot_table "$TEST_IMG"
    279 
    280 echo
    281 echo '=== Remove different snapshots ==='
    282 
    283 for sn in sn0 sn1 sn2; do
    284     echo
    285     echo "--- $sn ---"
    286 
    287     cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
    288     $QEMU_IMG snapshot -d $sn "$TEST_IMG"
    289     _check_test_img
    290     print_snapshot_table "$TEST_IMG"
    291 done
    292 
    293 echo
    294 echo '=== Reject too much unknown extra data ==='
    295 echo
    296 
    297 cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
    298 $QEMU_IMG snapshot -c sn3 "$TEST_IMG"
    299 
    300 sn_table_ofs=$(peek_file_be "$TEST_IMG" 64 8)
    301 sn0_ofs=$sn_table_ofs
    302 sn1_ofs=$((sn0_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn0_ofs)))
    303 sn2_ofs=$((sn1_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn1_ofs)))
    304 sn3_ofs=$((sn2_ofs + $(snapshot_table_entry_size "$TEST_IMG" $sn2_ofs)))
    305 
    306 # 64 kB of extra data should be rejected
    307 # (Note that this also induces a refcount error, because it spills
    308 # over to the next cluster.  That's a good way to test that we can
    309 # handle simultaneous snapshot table and refcount errors.)
    310 poke_file "$TEST_IMG" $((sn3_ofs + 36)) '\x00\x01\x00\x00'
    311 
    312 # Print error
    313 _img_info
    314 echo
    315 _check_test_img
    316 echo
    317 
    318 # Should be repairable
    319 _check_test_img -r all
    320 
    321 echo
    322 echo '=== Snapshot table too big ==='
    323 echo
    324 
    325 sn_table_ofs=$(peek_file_be "$TEST_IMG.v3.orig" 64 8)
    326 
    327 # Fill a snapshot with 1 kB of extra data, a 65535-char ID, and a
    328 # 65535-char name, and repeat it as many times as necessary to fill
    329 # 64 MB (the maximum supported by qemu)
    330 
    331 touch "$TEST_DIR/sn0"
    332 
    333 # Full size (fixed + extra + ID + name + padding)
    334 sn_size=$((40 + 1024 + 65535 + 65535 + 2))
    335 
    336 # We only need the fixed part, though.
    337 truncate -s 40 "$TEST_DIR/sn0"
    338 
    339 # 65535-char ID string
    340 poke_file "$TEST_DIR/sn0" 12 '\xff\xff'
    341 # 65535-char name
    342 poke_file "$TEST_DIR/sn0" 14 '\xff\xff'
    343 # 1 kB of extra data
    344 poke_file "$TEST_DIR/sn0" 36 '\x00\x00\x04\x00'
    345 
    346 # Create test image
    347 _make_test_img 64M
    348 
    349 # Hook up snapshot table somewhere safe (at 1 MB)
    350 poke_file "$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
    351 
    352 offset=1048576
    353 size_written=0
    354 sn_count=0
    355 while [ $size_written -le $((64 * 1048576)) ]; do
    356     dd if="$TEST_DIR/sn0" of="$TEST_IMG" bs=1 seek=$offset conv=notrunc \
    357         &> /dev/null
    358     offset=$((offset + sn_size))
    359     size_written=$((size_written + sn_size))
    360     sn_count=$((sn_count + 1))
    361 done
    362 truncate -s "$offset" "$TEST_IMG"
    363 
    364 # Give the last snapshot (the one to be removed) an L1 table so we can
    365 # see how that is handled when repairing the image
    366 # (Put it two clusters before 1 MB, and one L2 table one cluster
    367 # before 1 MB)
    368 poke_file "$TEST_IMG" $((offset - sn_size + 0)) \
    369     '\x00\x00\x00\x00\x00\x0e\x00\x00'
    370 poke_file "$TEST_IMG" $((offset - sn_size + 8)) \
    371     '\x00\x00\x00\x01'
    372 
    373 # Hook up the L2 table
    374 poke_file "$TEST_IMG" $((1048576 - 2 * 65536)) \
    375     '\x80\x00\x00\x00\x00\x0f\x00\x00'
    376 
    377 # Make sure all of the clusters we just hooked up are allocated:
    378 # - The snapshot table
    379 # - The last snapshot's L1 and L2 table
    380 refblock0_allocate $((1048576 - 2 * 65536)) $offset
    381 
    382 poke_file "$TEST_IMG" 60 \
    383     "$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
    384 
    385 # Print error
    386 _img_info
    387 echo
    388 _check_test_img
    389 echo
    390 
    391 # Should be repairable
    392 _check_test_img -r all
    393 
    394 echo
    395 echo "$((sn_count - 1)) snapshots should remain:"
    396 echo "  qemu-img info reports $(_img_info | grep -c '^ \{32\}') snapshots"
    397 echo "  Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
    398 
    399 echo
    400 echo '=== Snapshot table too big with one entry with too much extra data ==='
    401 echo
    402 
    403 # For this test, we reuse the image from the previous case, which has
    404 # a snapshot table that is right at the limit.
    405 # Our layout looks like this:
    406 # - (a number of snapshot table entries)
    407 # - One snapshot with $extra_data_size extra data
    408 # - One normal snapshot that breaks the 64 MB boundary
    409 # - One normal snapshot beyond the 64 MB boundary
    410 #
    411 # $extra_data_size is calculated so that simply by virtue of it
    412 # decreasing to 1 kB, the penultimate snapshot will fit into 64 MB
    413 # limit again.  The final snapshot will always be beyond the limit, so
    414 # that we can see that the repair algorithm does still determine the
    415 # limit to be somewhere, even when truncating one snapshot's extra
    416 # data.
    417 
    418 # The last case has removed the last snapshot, so calculate
    419 # $old_offset to get the current image's real length
    420 old_offset=$((offset - sn_size))
    421 
    422 # The layout from the previous test had one snapshot beyond the 64 MB
    423 # limit; we want the same (after the oversized extra data has been
    424 # truncated to 1 kB), so we drop the last three snapshots and
    425 # construct them from scratch.
    426 offset=$((offset - 3 * sn_size))
    427 sn_count=$((sn_count - 3))
    428 
    429 # Assuming we had already written one of the three snapshots
    430 # (necessary so we can calculate $extra_data_size next).
    431 size_written=$((size_written - 2 * sn_size))
    432 
    433 # Increase the extra data size so we go past the limit
    434 # (The -1024 comes from the 1 kB of extra data we already have)
    435 extra_data_size=$((64 * 1048576 + 8 - sn_size - (size_written - 1024)))
    436 
    437 poke_file "$TEST_IMG" $((offset + 36)) \
    438     "$(printf '%08x' $extra_data_size | sed -e 's/\(..\)/\\x\1/g')"
    439 
    440 offset=$((offset + sn_size - 1024 + extra_data_size))
    441 size_written=$((size_written - 1024 + extra_data_size))
    442 sn_count=$((sn_count + 1))
    443 
    444 # Write the two normal snapshots
    445 for ((i = 0; i < 2; i++)); do
    446     dd if="$TEST_DIR/sn0" of="$TEST_IMG" bs=1 seek=$offset conv=notrunc \
    447         &> /dev/null
    448     offset=$((offset + sn_size))
    449     size_written=$((size_written + sn_size))
    450     sn_count=$((sn_count + 1))
    451 
    452     if [ $i = 0 ]; then
    453         # Check that the penultimate snapshot is beyond the 64 MB limit
    454         echo "Snapshot table size should equal $((64 * 1048576 + 8)):" \
    455             $size_written
    456         echo
    457     fi
    458 done
    459 
    460 truncate -s $offset "$TEST_IMG"
    461 refblock0_allocate $old_offset $offset
    462 
    463 poke_file "$TEST_IMG" 60 \
    464     "$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
    465 
    466 # Print error
    467 _img_info
    468 echo
    469 _check_test_img
    470 echo
    471 
    472 # Just truncating the extra data should be sufficient to shorten the
    473 # snapshot table so only one snapshot exceeds the extra size
    474 _check_test_img -r all
    475 
    476 echo
    477 echo '=== Too many snapshots ==='
    478 echo
    479 
    480 # Create a v2 image, for speeds' sake: All-zero snapshot table entries
    481 # are only valid in v2.
    482 IMGOPTS='compat=0.10' _make_test_img 64M
    483 
    484 # Hook up snapshot table somewhere safe (at 1 MB)
    485 poke_file "$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
    486 # "Create" more than 65536 snapshots (twice that many here)
    487 poke_file "$TEST_IMG" 60 '\x00\x02\x00\x00'
    488 
    489 # 40-byte all-zero snapshot table entries are valid snapshots, but
    490 # only in v2 (v3 needs 16 bytes of extra data, so we would have to
    491 # write 131072x '\x10').
    492 truncate -s $((1048576 + 40 * 131072)) "$TEST_IMG"
    493 
    494 # But let us give one of the snapshots to be removed an L1 table so
    495 # we can see how that is handled when repairing the image.
    496 # (Put it two clusters before 1 MB, and one L2 table one cluster
    497 # before 1 MB)
    498 poke_file "$TEST_IMG" $((1048576 + 40 * 65536 + 0)) \
    499     '\x00\x00\x00\x00\x00\x0e\x00\x00'
    500 poke_file "$TEST_IMG" $((1048576 + 40 * 65536 + 8)) \
    501     '\x00\x00\x00\x01'
    502 
    503 # Hook up the L2 table
    504 poke_file "$TEST_IMG" $((1048576 - 2 * 65536)) \
    505     '\x80\x00\x00\x00\x00\x0f\x00\x00'
    506 
    507 # Make sure all of the clusters we just hooked up are allocated:
    508 # - The snapshot table
    509 # - The last snapshot's L1 and L2 table
    510 refblock0_allocate $((1048576 - 2 * 65536)) $((1048576 + 40 * 131072))
    511 
    512 # Print error
    513 _img_info
    514 echo
    515 _check_test_img
    516 echo
    517 
    518 # Should be repairable
    519 _check_test_img -r all
    520 
    521 echo
    522 echo '65536 snapshots should remain:'
    523 echo "  qemu-img info reports $(_img_info | grep -c '^ \{32\}') snapshots"
    524 echo "  Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
    525 
    526 # success, all done
    527 echo "*** done"
    528 status=0