@@ -5,6 +5,122 @@ import sys
import struct
import string
+
+class Qcow2BitmapDirEntry:
+
+ name = ''
+ BME_FLAG_IN_USE = 1
+ BME_FLAG_AUTO = 1 << 1
+
+ uint8_t = 'B'
+ uint16_t = 'H'
+ uint32_t = 'I'
+ uint64_t = 'Q'
+
+ fields = [
+ [uint64_t, '%#x', 'bitmap_table_offset'],
+ [uint32_t, '%d', 'bitmap_table_size'],
+ [uint32_t, '%d', 'flags'],
+ [uint8_t, '%d', 'type'],
+ [uint8_t, '%d', 'granularity_bits'],
+ [uint16_t, '%d', 'name_size'],
+ [uint32_t, '%d', 'extra_data_size']
+ ]
+
+ fmt = '>' + ''.join(field[0] for field in fields)
+
+ def __init__(self, data):
+
+ entry = struct.unpack(Qcow2BitmapDirEntry.fmt, data)
+ self.__dict__ = dict((field[2], entry[i])
+ for i, field in enumerate(
+ Qcow2BitmapDirEntry.fields))
+
+ self.bitmap_table_size = self.bitmap_table_size \
+ * struct.calcsize(self.uint64_t)
+
+ def bitmap_dir_entry_size(self):
+ size = struct.calcsize(self.fmt) + self.name_size + \
+ self.extra_data_size
+ return (size + 7) & ~7
+
+ def dump_bitmap_dir_entry(self):
+ print("%-25s" % 'Bitmap name', self.name)
+ if (self.flags & self.BME_FLAG_IN_USE) != 0:
+ print("%-25s" % 'flag', '"in-use"')
+ if (self.flags & self.BME_FLAG_AUTO) != 0:
+ print("%-25s" % 'flag', '"auto"')
+ for f in Qcow2BitmapDirEntry.fields:
+ value = self.__dict__[f[2]]
+ value_str = f[1] % value
+
+ print("%-25s" % f[2], value_str)
+ print("")
+
+ def dump_bitmap_table(self, fd):
+ fd.seek(self.bitmap_table_offset)
+ table_size = self.bitmap_table_size * struct.calcsize(self.uint64_t)
+ bitmap_table = [e[0] for e in struct.iter_unpack('>Q',
+ fd.read(table_size))]
+ BME_TABLE_ENTRY_OFFSET_MASK = 0x00fffffffffffe00
+ BME_TABLE_ENTRY_FLAG_ALL_ONES = 1
+ bmt_type = ['all-zeroes', 'all-ones', 'serialized']
+ items = enumerate(bitmap_table)
+ print("Bitmap table")
+ for i, entry in items:
+ offset = entry & BME_TABLE_ENTRY_OFFSET_MASK
+ if offset != 0:
+ index = 2
+ else:
+ index = entry & BME_TABLE_ENTRY_FLAG_ALL_ONES
+ print(" %-4d %s, offset %#x" % (i, bmt_type[index], offset))
+ print("")
+
+
+class Qcow2BitmapExt:
+
+ uint32_t = 'I'
+ uint64_t = 'Q'
+
+ fields = [
+ [uint32_t, '%d', 'nb_bitmaps'],
+ [uint32_t, '%d', 'reserved32'],
+ [uint64_t, '%#x', 'bitmap_directory_size'],
+ [uint64_t, '%#x', 'bitmap_directory_offset']
+ ]
+
+ fmt = '>' + ''.join(field[0] for field in fields)
+
+ def __init__(self, data):
+
+ extension = struct.unpack(Qcow2BitmapExt.fmt, data)
+ self.__dict__ = dict((field[2], extension[i])
+ for i, field in enumerate(Qcow2BitmapExt.fields))
+
+ def dump_bitmap_ext(self):
+ for f in Qcow2BitmapExt.fields:
+ value = self.__dict__[f[2]]
+ value_str = f[1] % value
+
+ print("%-25s" % f[2], value_str)
+ print("")
+
+ def bitmap_directory(self, fd):
+ offset = self.bitmap_directory_offset
+ buf_size = struct.calcsize(Qcow2BitmapDirEntry.fmt)
+
+ for n in range(self.nb_bitmaps):
+ fd.seek(offset)
+ buf = fd.read(buf_size)
+ dir_entry = Qcow2BitmapDirEntry(buf)
+ fd.seek(dir_entry.extra_data_size, 1)
+ bitmap_name = fd.read(dir_entry.name_size)
+ dir_entry.name = bitmap_name.decode('ascii')
+ dir_entry.dump_bitmap_dir_entry()
+ dir_entry.dump_bitmap_table(fd)
+ offset += dir_entry.bitmap_dir_entry_size()
+
+
class QcowHeaderExtension:
def __init__(self, magic, length, data):
@@ -22,6 +138,8 @@ class QcowHeaderExtension:
class QcowHeader:
+ QCOW2_EXT_MAGIC_FEATURE_TABLE = 0x6803f857
+ QCOW2_EXT_MAGIC_BITMAPS = 0x23852875
uint32_t = 'I'
uint64_t = 'Q'
@@ -128,6 +246,12 @@ class QcowHeader:
buf = buf[0:header_bytes-1]
fd.write(buf)
+ def extension_name(self, magic):
+ return {
+ self.QCOW2_EXT_MAGIC_FEATURE_TABLE: 'Feature table',
+ self.QCOW2_EXT_MAGIC_BITMAPS: 'Bitmaps',
+ }.get(magic, 'Unknown')
+
def dump(self):
for f in QcowHeader.fields:
value = self.__dict__[f[2]]
@@ -143,30 +267,39 @@ class QcowHeader:
print("%-25s" % f[2], value_str)
print("")
- def dump_extensions(self):
+ def dump_extensions(self, fd):
for ex in self.extensions:
+ print("Header extension (%s):" % self.extension_name(ex.magic))
+ print("%-25s %#x" % ("magic", ex.magic))
+ print("%-25s %d" % ("length", ex.length))
+
data = ex.data[:ex.length]
if all(c in string.printable.encode('ascii') for c in data):
data = "'%s'" % data.decode('ascii')
+ print("%-25s %s" % ("data", data))
else:
- data = "<binary>"
+ self.dump_extension_data(fd, ex)
- print("Header extension:")
- print("%-25s %#x" % ("magic", ex.magic))
- print("%-25s %d" % ("length", ex.length))
- print("%-25s %s" % ("data", data))
print("")
+ def dump_extension_data(self, fd, ext):
+ if ext.magic == self.QCOW2_EXT_MAGIC_BITMAPS:
+ b_ext = Qcow2BitmapExt(ext.data)
+ b_ext.dump_bitmap_ext()
+ b_ext.bitmap_directory(fd)
+ else:
+ print("%-25s %s" % ("data", "<binary>"))
+
def cmd_dump_header(fd):
h = QcowHeader(fd)
h.dump()
- h.dump_extensions()
+ h.dump_extensions(fd)
def cmd_dump_header_exts(fd):
h = QcowHeader(fd)
- h.dump_extensions()
+ h.dump_extensions(fd)
def cmd_set_header(fd, name, value):
try:
Add dirty bitmap information to QCOW2 metadata dump in qcow2.py script. The sample output: Header extension (Bitmaps): magic 0x23852875 length 24 nb_bitmaps 2 reserved32 0 bitmap_directory_size 0x40 bitmap_directory_offset 0x100000 Bitmap name bitmap-1 flag "auto" bitmap_table_offset 0x90000 bitmap_table_size 8 flags 2 type 1 granularity_bits 15 name_size 8 extra_data_size 0 Bitmap table 0 serialized, offset 0xa0000 1 all-zeroes, offset 0x0 2 all-zeroes, offset 0x0 3 all-zeroes, offset 0x0 4 all-zeroes, offset 0x0 5 all-zeroes, offset 0x0 6 all-zeroes, offset 0x0 7 all-zeroes, offset 0x0 Signed-off-by: Andrey Shinkevich <andrey.shinkevich@virtuozzo.com> --- tests/qemu-iotests/qcow2.py | 149 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 8 deletions(-)