qcow2_format.py: dump bitmaps header extension

Add class for bitmap extension and dump its fields. Further work is to
dump bitmap directory.

Test new functionality inside 291 iotest.

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Reviewed-by: Andrey Shinkevich <andrey.shinkevich@virtuozzo.com>
Message-Id: <20200606081806.23897-14-vsementsov@virtuozzo.com>
[eblake: fix iotest output]
Signed-off-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
Vladimir Sementsov-Ogievskiy 2020-06-06 11:18:06 +03:00 committed by Eric Blake
parent aef87784f9
commit 820c6bee53
3 changed files with 70 additions and 9 deletions

View File

@ -62,6 +62,8 @@ $QEMU_IO -c 'w 1M 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
$QEMU_IMG bitmap --disable -f $IMGFMT "$TEST_IMG" b1 $QEMU_IMG bitmap --disable -f $IMGFMT "$TEST_IMG" b1
$QEMU_IMG bitmap --enable -f $IMGFMT "$TEST_IMG" b2 $QEMU_IMG bitmap --enable -f $IMGFMT "$TEST_IMG" b2
$QEMU_IO -c 'w 2M 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io $QEMU_IO -c 'w 2M 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
echo "Check resulting qcow2 header extensions:"
$PYTHON qcow2.py "$TEST_IMG" dump-header-exts
echo echo
echo "=== Bitmap preservation not possible to non-qcow2 ===" echo "=== Bitmap preservation not possible to non-qcow2 ==="
@ -88,6 +90,8 @@ $QEMU_IMG bitmap --merge tmp -f $IMGFMT "$TEST_IMG" b0
$QEMU_IMG bitmap --remove --image-opts \ $QEMU_IMG bitmap --remove --image-opts \
driver=$IMGFMT,file.driver=file,file.filename="$TEST_IMG" tmp driver=$IMGFMT,file.driver=file,file.filename="$TEST_IMG" tmp
$QEMU_IMG info "$TEST_IMG" | _filter_img_info --format-specific $QEMU_IMG info "$TEST_IMG" | _filter_img_info --format-specific
echo "Check resulting qcow2 header extensions:"
$PYTHON qcow2.py "$TEST_IMG" dump-header-exts
echo echo
echo "=== Check bitmap contents ===" echo "=== Check bitmap contents ==="

View File

@ -14,6 +14,25 @@ wrote 1048576/1048576 bytes at offset 1048576
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
wrote 1048576/1048576 bytes at offset 2097152 wrote 1048576/1048576 bytes at offset 2097152
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
Check resulting qcow2 header extensions:
Header extension:
magic 0xe2792aca (Backing format)
length 5
data 'qcow2'
Header extension:
magic 0x6803f857 (Feature table)
length 336
data <binary>
Header extension:
magic 0x23852875 (Bitmaps)
length 24
nb_bitmaps 2
reserved32 0
bitmap_directory_size 0x40
bitmap_directory_offset 0x510000
=== Bitmap preservation not possible to non-qcow2 === === Bitmap preservation not possible to non-qcow2 ===
@ -65,6 +84,20 @@ Format specific information:
granularity: 65536 granularity: 65536
refcount bits: 16 refcount bits: 16
corrupt: false corrupt: false
Check resulting qcow2 header extensions:
Header extension:
magic 0x6803f857 (Feature table)
length 336
data <binary>
Header extension:
magic 0x23852875 (Bitmaps)
length 24
nb_bitmaps 3
reserved32 0
bitmap_directory_size 0x60
bitmap_directory_offset 0x520000
=== Check bitmap contents === === Check bitmap contents ===

View File

@ -103,6 +103,19 @@ class Qcow2Struct(metaclass=Qcow2StructMeta):
print('{:<25} {}'.format(f[2], value_str)) print('{:<25} {}'.format(f[2], value_str))
class Qcow2BitmapExt(Qcow2Struct):
fields = (
('u32', '{}', 'nb_bitmaps'),
('u32', '{}', 'reserved32'),
('u64', '{:#x}', 'bitmap_directory_size'),
('u64', '{:#x}', 'bitmap_directory_offset')
)
QCOW2_EXT_MAGIC_BITMAPS = 0x23852875
class QcowHeaderExtension(Qcow2Struct): class QcowHeaderExtension(Qcow2Struct):
class Magic(Enum): class Magic(Enum):
@ -110,7 +123,7 @@ class QcowHeaderExtension(Qcow2Struct):
0xe2792aca: 'Backing format', 0xe2792aca: 'Backing format',
0x6803f857: 'Feature table', 0x6803f857: 'Feature table',
0x0537be77: 'Crypto header', 0x0537be77: 'Crypto header',
0x23852875: 'Bitmaps', QCOW2_EXT_MAGIC_BITMAPS: 'Bitmaps',
0x44415441: 'Data file' 0x44415441: 'Data file'
} }
@ -130,8 +143,11 @@ class QcowHeaderExtension(Qcow2Struct):
This should be somehow refactored and functionality should be moved to This should be somehow refactored and functionality should be moved to
superclass (to allow creation of any qcow2 struct), but then, fields superclass (to allow creation of any qcow2 struct), but then, fields
of variable length (data here) should be supported in base class of variable length (data here) should be supported in base class
somehow. So, it's a TODO. We'll see how to properly refactor this when somehow. Note also, that we probably want to parse different
we have more qcow2 structures. extensions. Should they be subclasses of this class, or how to do it
better? Should it be something like QAPI union with discriminator field
(magic here). So, it's a TODO. We'll see how to properly refactor this
when we have more qcow2 structures.
""" """
if fd is None: if fd is None:
assert all(v is not None for v in (magic, length, data)) assert all(v is not None for v in (magic, length, data))
@ -148,15 +164,23 @@ class QcowHeaderExtension(Qcow2Struct):
self.data = fd.read(padded) self.data = fd.read(padded)
assert self.data is not None assert self.data is not None
def dump(self): if self.magic == QCOW2_EXT_MAGIC_BITMAPS:
data = self.data[:self.length] self.obj = Qcow2BitmapExt(data=self.data)
if all(c in string.printable.encode('ascii') for c in data):
data = f"'{ data.decode('ascii') }'"
else: else:
data = '<binary>' self.obj = None
def dump(self):
super().dump() super().dump()
print(f'{"data":<25} {data}')
if self.obj is None:
data = self.data[:self.length]
if all(c in string.printable.encode('ascii') for c in data):
data = f"'{ data.decode('ascii') }'"
else:
data = '<binary>'
print(f'{"data":<25} {data}')
else:
self.obj.dump()
@classmethod @classmethod
def create(cls, magic, data): def create(cls, magic, data):