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
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/>.
24 owner
=mreitz@redhat.com
27 echo "QA output created by $seq"
29 status
=1 # failure is the default!
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
43 # This tests qcow2-specific low-level functionality
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
50 # As for data files, they do not support snapshots at all.
51 _unsupported_imgopts
'refcount_bits=\([^1]\|.\([^6]\|$\)\)' data_file
55 # $2: snapshot table entry offset in the image
56 snapshot_table_entry_size
()
58 id_len
=$
(peek_file_be
"$1" $
(($2 + 12)) 2)
59 name_len
=$
(peek_file_be
"$1" $
(($2 + 14)) 2)
60 extra_len
=$
(peek_file_be
"$1" $
(($2 + 36)) 4)
62 full_len
=$
((40 + extra_len
+ id_len
+ name_len
))
63 echo $
(((full_len
+ 7) / 8 * 8))
68 print_snapshot_table
()
70 nb_entries
=$
(peek_file_be
"$1" 60 4)
71 offset
=$
(peek_file_be
"$1" 64 8)
73 echo "Snapshots in $1:" | _filter_testdir | _filter_imgfmt
75 for ((i
= 0; i
< nb_entries
; i
++)); do
76 id_len
=$
(peek_file_be
"$1" $
((offset
+ 12)) 2)
77 name_len
=$
(peek_file_be
"$1" $
((offset
+ 14)) 2)
78 extra_len
=$
(peek_file_be
"$1" $
((offset
+ 36)) 4)
80 extra_ofs
=$
((offset
+ 40))
81 id_ofs
=$
((extra_ofs
+ extra_len
))
82 name_ofs
=$
((id_ofs
+ id_len
))
85 echo " ID: $(peek_file_raw "$1" $id_ofs $id_len)"
86 echo " Name: $(peek_file_raw "$1" $name_ofs $name_len)"
87 echo " Extra data size: $extra_len"
88 if [ $extra_len -ge 8 ]; then
89 echo " VM state size: $(peek_file_be "$1" $extra_ofs 8)"
91 if [ $extra_len -ge 16 ]; then
92 echo " Disk size: $(peek_file_be "$1" $((extra_ofs + 8)) 8)"
94 if [ $extra_len -ge 24 ]; then
95 echo " Icount: $(peek_file_be "$1" $((extra_ofs + 16)) 8)"
97 if [ $extra_len -gt 24 ]; then
98 echo ' Unknown extra data:' \
99 "$(peek_file_raw "$1" $((extra_ofs + 16)) $((extra_len - 16)) \
103 offset
=$
((offset
+ $
(snapshot_table_entry_size
"$1" $offset)))
107 # Mark clusters as allocated; works only in refblock 0 (i.e. before
110 # $1: Start offset of what to allocate
111 # $2: End offset (exclusive)
114 reftable_ofs
=$
(peek_file_be
"$TEST_IMG" 48 8)
115 refblock_ofs
=$
(peek_file_be
"$TEST_IMG" $reftable_ofs 8)
117 cluster
=$
(($1 / 65536))
118 ecluster
=$
((($2 + 65535) / 65536))
120 while [ $cluster -lt $ecluster ]; do
121 if [ $cluster -ge 32768 ]; then
122 echo "*** Abort: Cluster $cluster exceeds refblock 0 ***"
125 poke_file
"$TEST_IMG" $
((refblock_ofs
+ cluster
* 2)) '\x00\x01'
126 cluster
=$
((cluster
+ 1))
132 echo '=== Create v2 template ==='
135 # Create v2 image with a snapshot table with three entries:
136 # [0]: No extra data (valid with v2, not valid with v3)
137 # [1]: Has extra data unknown to qemu
138 # [2]: Has the 64-bit VM state size, but not the disk size (again,
139 # valid with v2, not valid with v3)
141 TEST_IMG
="$TEST_IMG.v2.orig" IMGOPTS
='compat=0.10' _make_test_img
64M
142 $QEMU_IMG snapshot
-c sn0
"$TEST_IMG.v2.orig"
143 $QEMU_IMG snapshot
-c sn1
"$TEST_IMG.v2.orig"
144 $QEMU_IMG snapshot
-c sn2
"$TEST_IMG.v2.orig"
146 # Copy out all existing snapshot table entries
147 sn_table_ofs
=$
(peek_file_be
"$TEST_IMG.v2.orig" 64 8)
149 # ofs: Snapshot table entry offset
150 # eds: Extra data size
151 # ids: Name + ID size
152 # len: Total entry length
153 sn0_ofs
=$sn_table_ofs
154 sn0_eds
=$
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn0_ofs
+ 36)) 4)
155 sn0_ids
=$
(($
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn0_ofs
+ 12)) 2) +
156 $
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn0_ofs
+ 14)) 2)))
157 sn0_len
=$
(snapshot_table_entry_size
"$TEST_IMG.v2.orig" $sn0_ofs)
158 sn1_ofs
=$
((sn0_ofs
+ sn0_len
))
159 sn1_eds
=$
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn1_ofs
+ 36)) 4)
160 sn1_ids
=$
(($
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn1_ofs
+ 12)) 2) +
161 $
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn1_ofs
+ 14)) 2)))
162 sn1_len
=$
(snapshot_table_entry_size
"$TEST_IMG.v2.orig" $sn1_ofs)
163 sn2_ofs
=$
((sn1_ofs
+ sn1_len
))
164 sn2_eds
=$
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn2_ofs
+ 36)) 4)
165 sn2_ids
=$
(($
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn2_ofs
+ 12)) 2) +
166 $
(peek_file_be
"$TEST_IMG.v2.orig" $
((sn2_ofs
+ 14)) 2)))
167 sn2_len
=$
(snapshot_table_entry_size
"$TEST_IMG.v2.orig" $sn2_ofs)
169 # Data before extra data
170 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn0-pre" bs
=1 skip
=$sn0_ofs count
=40 \
172 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn1-pre" bs
=1 skip
=$sn1_ofs count
=40 \
174 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn2-pre" bs
=1 skip
=$sn2_ofs count
=40 \
178 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn0-extra" bs
=1 \
179 skip
=$
((sn0_ofs
+ 40)) count
=$sn0_eds &> /dev
/null
180 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn1-extra" bs
=1 \
181 skip
=$
((sn1_ofs
+ 40)) count
=$sn1_eds &> /dev
/null
182 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn2-extra" bs
=1 \
183 skip
=$
((sn2_ofs
+ 40)) count
=$sn2_eds &> /dev
/null
185 # Data after extra data
186 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn0-post" bs
=1 \
187 skip
=$
((sn0_ofs
+ 40 + sn0_eds
)) count
=$sn0_ids \
189 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn1-post" bs
=1 \
190 skip
=$
((sn1_ofs
+ 40 + sn1_eds
)) count
=$sn1_ids \
192 dd if="$TEST_IMG.v2.orig" of
="$TEST_DIR/sn2-post" bs
=1 \
193 skip
=$
((sn2_ofs
+ 40 + sn2_eds
)) count
=$sn2_ids \
196 # Amend them, one by one
197 # Set sn0's extra data size to 0
198 poke_file
"$TEST_DIR/sn0-pre" 36 '\x00\x00\x00\x00'
199 truncate
-s 0 "$TEST_DIR/sn0-extra"
200 # Grow sn0-post to pad
201 truncate
-s $
(($
(snapshot_table_entry_size
"$TEST_DIR/sn0-pre") - 40)) \
204 # Set sn1's extra data size to 50
205 poke_file
"$TEST_DIR/sn1-pre" 36 '\x00\x00\x00\x32'
206 truncate
-s 50 "$TEST_DIR/sn1-extra"
207 poke_file
"$TEST_DIR/sn1-extra" 24 'very important data'
208 # Grow sn1-post to pad
209 truncate
-s $
(($
(snapshot_table_entry_size
"$TEST_DIR/sn1-pre") - 90)) \
212 # Set sn2's extra data size to 8
213 poke_file
"$TEST_DIR/sn2-pre" 36 '\x00\x00\x00\x08'
214 truncate
-s 8 "$TEST_DIR/sn2-extra"
215 # Grow sn2-post to pad
216 truncate
-s $
(($
(snapshot_table_entry_size
"$TEST_DIR/sn2-pre") - 48)) \
219 # Construct snapshot table
220 cat "$TEST_DIR"/sn0-
{pre
,extra
,post
} \
221 "$TEST_DIR"/sn1-
{pre
,extra
,post
} \
222 "$TEST_DIR"/sn2-
{pre
,extra
,post
} \
223 |
dd of
="$TEST_IMG.v2.orig" bs
=1 seek
=$sn_table_ofs conv
=notrunc \
227 TEST_IMG
="$TEST_IMG.v2.orig" _check_test_img
228 print_snapshot_table
"$TEST_IMG.v2.orig"
231 echo '=== Upgrade to v3 ==='
234 cp "$TEST_IMG.v2.orig" "$TEST_IMG.v3.orig"
235 $QEMU_IMG amend
-o compat
=1.1 "$TEST_IMG.v3.orig"
236 TEST_IMG
="$TEST_IMG.v3.orig" _check_test_img
237 print_snapshot_table
"$TEST_IMG.v3.orig"
240 echo '=== Repair botched v3 ==='
243 # Force the v2 file to be v3. v3 requires each snapshot table entry
244 # to have at least 16 bytes of extra data, so it will not comply to
245 # the qcow2 v3 specification; but we can fix that.
246 cp "$TEST_IMG.v2.orig" "$TEST_IMG"
249 poke_file
"$TEST_IMG" 4 '\x00\x00\x00\x03'
250 # Increase header length (necessary for v3)
251 poke_file
"$TEST_IMG" 100 '\x00\x00\x00\x68'
252 # Set refcount order (necessary for v3)
253 poke_file
"$TEST_IMG" 96 '\x00\x00\x00\x04'
255 _check_test_img
-r all
256 print_snapshot_table
"$TEST_IMG"
259 # From now on, just test the qcow2 version we are supposed to test.
260 # (v3 by default, v2 by choice through $IMGOPTS.)
261 # That works because we always write all known extra data when
262 # updating the snapshot table, independent of the version.
264 if echo "$IMGOPTS" |
grep -q 'compat=\(0\.10\|v2\)' 2> /dev
/null
; then
271 echo '=== Add new snapshot ==='
274 cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
275 $QEMU_IMG snapshot
-c sn3
"$TEST_IMG"
277 print_snapshot_table
"$TEST_IMG"
280 echo '=== Remove different snapshots ==='
282 for sn
in sn0 sn1 sn2
; do
286 cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
287 $QEMU_IMG snapshot
-d $sn "$TEST_IMG"
289 print_snapshot_table
"$TEST_IMG"
293 echo '=== Reject too much unknown extra data ==='
296 cp "$TEST_IMG.$subver.orig" "$TEST_IMG"
297 $QEMU_IMG snapshot
-c sn3
"$TEST_IMG"
299 sn_table_ofs
=$
(peek_file_be
"$TEST_IMG" 64 8)
300 sn0_ofs
=$sn_table_ofs
301 sn1_ofs
=$
((sn0_ofs
+ $
(snapshot_table_entry_size
"$TEST_IMG" $sn0_ofs)))
302 sn2_ofs
=$
((sn1_ofs
+ $
(snapshot_table_entry_size
"$TEST_IMG" $sn1_ofs)))
303 sn3_ofs
=$
((sn2_ofs
+ $
(snapshot_table_entry_size
"$TEST_IMG" $sn2_ofs)))
305 # 64 kB of extra data should be rejected
306 # (Note that this also induces a refcount error, because it spills
307 # over to the next cluster. That's a good way to test that we can
308 # handle simultaneous snapshot table and refcount errors.)
309 poke_file
"$TEST_IMG" $
((sn3_ofs
+ 36)) '\x00\x01\x00\x00'
317 # Should be repairable
318 _check_test_img
-r all
321 echo '=== Snapshot table too big ==='
324 sn_table_ofs
=$
(peek_file_be
"$TEST_IMG.v3.orig" 64 8)
326 # Fill a snapshot with 1 kB of extra data, a 65535-char ID, and a
327 # 65535-char name, and repeat it as many times as necessary to fill
328 # 64 MB (the maximum supported by qemu)
330 touch "$TEST_DIR/sn0"
332 # Full size (fixed + extra + ID + name + padding)
333 sn_size
=$
((40 + 1024 + 65535 + 65535 + 2))
335 # We only need the fixed part, though.
336 truncate
-s 40 "$TEST_DIR/sn0"
338 # 65535-char ID string
339 poke_file
"$TEST_DIR/sn0" 12 '\xff\xff'
341 poke_file
"$TEST_DIR/sn0" 14 '\xff\xff'
343 poke_file
"$TEST_DIR/sn0" 36 '\x00\x00\x04\x00'
348 # Hook up snapshot table somewhere safe (at 1 MB)
349 poke_file
"$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
354 while [ $size_written -le $
((64 * 1048576)) ]; do
355 dd if="$TEST_DIR/sn0" of
="$TEST_IMG" bs
=1 seek
=$offset conv
=notrunc \
357 offset
=$
((offset
+ sn_size
))
358 size_written
=$
((size_written
+ sn_size
))
359 sn_count
=$
((sn_count
+ 1))
361 truncate
-s "$offset" "$TEST_IMG"
363 # Give the last snapshot (the one to be removed) an L1 table so we can
364 # see how that is handled when repairing the image
365 # (Put it two clusters before 1 MB, and one L2 table one cluster
367 poke_file
"$TEST_IMG" $
((offset
- sn_size
+ 0)) \
368 '\x00\x00\x00\x00\x00\x0e\x00\x00'
369 poke_file
"$TEST_IMG" $
((offset
- sn_size
+ 8)) \
372 # Hook up the L2 table
373 poke_file
"$TEST_IMG" $
((1048576 - 2 * 65536)) \
374 '\x80\x00\x00\x00\x00\x0f\x00\x00'
376 # Make sure all of the clusters we just hooked up are allocated:
377 # - The snapshot table
378 # - The last snapshot's L1 and L2 table
379 refblock0_allocate $
((1048576 - 2 * 65536)) $offset
381 poke_file
"$TEST_IMG" 60 \
382 "$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
390 # Should be repairable
391 _check_test_img
-r all
394 echo "$((sn_count - 1)) snapshots should remain:"
395 echo " qemu-img info reports $(_img_info | grep -c '^ \{32\}') snapshots"
396 echo " Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"
399 echo '=== Snapshot table too big with one entry with too much extra data ==='
402 # For this test, we reuse the image from the previous case, which has
403 # a snapshot table that is right at the limit.
404 # Our layout looks like this:
405 # - (a number of snapshot table entries)
406 # - One snapshot with $extra_data_size extra data
407 # - One normal snapshot that breaks the 64 MB boundary
408 # - One normal snapshot beyond the 64 MB boundary
410 # $extra_data_size is calculated so that simply by virtue of it
411 # decreasing to 1 kB, the penultimate snapshot will fit into 64 MB
412 # limit again. The final snapshot will always be beyond the limit, so
413 # that we can see that the repair algorithm does still determine the
414 # limit to be somewhere, even when truncating one snapshot's extra
417 # The last case has removed the last snapshot, so calculate
418 # $old_offset to get the current image's real length
419 old_offset
=$
((offset
- sn_size
))
421 # The layout from the previous test had one snapshot beyond the 64 MB
422 # limit; we want the same (after the oversized extra data has been
423 # truncated to 1 kB), so we drop the last three snapshots and
424 # construct them from scratch.
425 offset
=$
((offset
- 3 * sn_size
))
426 sn_count
=$
((sn_count
- 3))
428 # Assuming we had already written one of the three snapshots
429 # (necessary so we can calculate $extra_data_size next).
430 size_written
=$
((size_written
- 2 * sn_size
))
432 # Increase the extra data size so we go past the limit
433 # (The -1024 comes from the 1 kB of extra data we already have)
434 extra_data_size
=$
((64 * 1048576 + 8 - sn_size
- (size_written
- 1024)))
436 poke_file
"$TEST_IMG" $
((offset
+ 36)) \
437 "$(printf '%08x' $extra_data_size | sed -e 's/\(..\)/\\x\1/g')"
439 offset
=$
((offset
+ sn_size
- 1024 + extra_data_size
))
440 size_written
=$
((size_written
- 1024 + extra_data_size
))
441 sn_count
=$
((sn_count
+ 1))
443 # Write the two normal snapshots
444 for ((i
= 0; i
< 2; i
++)); do
445 dd if="$TEST_DIR/sn0" of
="$TEST_IMG" bs
=1 seek
=$offset conv
=notrunc \
447 offset
=$
((offset
+ sn_size
))
448 size_written
=$
((size_written
+ sn_size
))
449 sn_count
=$
((sn_count
+ 1))
452 # Check that the penultimate snapshot is beyond the 64 MB limit
453 echo "Snapshot table size should equal $((64 * 1048576 + 8)):" \
459 truncate
-s $offset "$TEST_IMG"
460 refblock0_allocate
$old_offset $offset
462 poke_file
"$TEST_IMG" 60 \
463 "$(printf '%08x' $sn_count | sed -e 's/\(..\)/\\x\1/g')"
471 # Just truncating the extra data should be sufficient to shorten the
472 # snapshot table so only one snapshot exceeds the extra size
473 _check_test_img
-r all
476 echo '=== Too many snapshots ==='
479 # Create a v2 image, for speeds' sake: All-zero snapshot table entries
480 # are only valid in v2.
481 IMGOPTS
='compat=0.10' _make_test_img
64M
483 # Hook up snapshot table somewhere safe (at 1 MB)
484 poke_file
"$TEST_IMG" 64 '\x00\x00\x00\x00\x00\x10\x00\x00'
485 # "Create" more than 65536 snapshots (twice that many here)
486 poke_file
"$TEST_IMG" 60 '\x00\x02\x00\x00'
488 # 40-byte all-zero snapshot table entries are valid snapshots, but
489 # only in v2 (v3 needs 16 bytes of extra data, so we would have to
490 # write 131072x '\x10').
491 truncate
-s $
((1048576 + 40 * 131072)) "$TEST_IMG"
493 # But let us give one of the snapshots to be removed an L1 table so
494 # we can see how that is handled when repairing the image.
495 # (Put it two clusters before 1 MB, and one L2 table one cluster
497 poke_file
"$TEST_IMG" $
((1048576 + 40 * 65536 + 0)) \
498 '\x00\x00\x00\x00\x00\x0e\x00\x00'
499 poke_file
"$TEST_IMG" $
((1048576 + 40 * 65536 + 8)) \
502 # Hook up the L2 table
503 poke_file
"$TEST_IMG" $
((1048576 - 2 * 65536)) \
504 '\x80\x00\x00\x00\x00\x0f\x00\x00'
506 # Make sure all of the clusters we just hooked up are allocated:
507 # - The snapshot table
508 # - The last snapshot's L1 and L2 table
509 refblock0_allocate $
((1048576 - 2 * 65536)) $
((1048576 + 40 * 131072))
517 # Should be repairable
518 _check_test_img
-r all
521 echo '65536 snapshots should remain:'
522 echo " qemu-img info reports $(_img_info | grep -c '^ \{32\}') snapshots"
523 echo " Image header reports $(peek_file_be "$TEST_IMG" 60 4) snapshots"