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