Represent QCOW2 metadata dumping with qcow2.py script in JSON format { "QCOW2_header_extensions": [ { "Header_extension": "Feature table", "magic": "0x6803f857", "length": 192, "data_str": "<binary>" }, { "Header_extension": "Bitmaps", "magic": "0x23852875", "length": 24, "data": { "nb_bitmaps": 2, "reserved32": 0, "bitmap_directory_size": 64, "bitmap_directory_offset": 1048576, "entries": [ { "name": "bitmap-1", "flags": [], "flag_bits": 0, "bitmap_table_offset": 589824, "bitmap_table_size": 8, "type": 1, "granularity": 16, "name_size": 8, "extra_data_size": 0, "bitmap_table": { "table_entries": [ { "type": "serialized", "offset": 655360, "size": 65536 },
Suggested-by: Vladimir Sementsov-Ogievskiy <vsement...@virtuozzo.com> Signed-off-by: Andrey Shinkevich <andrey.shinkev...@virtuozzo.com> --- tests/qemu-iotests/qcow2.py | 108 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/tests/qemu-iotests/qcow2.py b/tests/qemu-iotests/qcow2.py index 76e0c69..fd1ef4f 100755 --- a/tests/qemu-iotests/qcow2.py +++ b/tests/qemu-iotests/qcow2.py @@ -3,11 +3,21 @@ import sys import struct import string +import json +dump_json = False cluster_size = 0 +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + if hasattr(obj, 'get_info_dict'): + return obj.get_info_dict() + else: + return json.JSONEncoder.default(self, obj) + + class Qcow2BitmapTableEntry: BME_TABLE_ENTRY_OFFSET_MASK = 0x00fffffffffffe00 @@ -23,6 +33,9 @@ class Qcow2BitmapTableEntry: index = entry & self.BME_TABLE_ENTRY_FLAG_ALL_ONES self.type = self.bmte_type[index] + def get_info_dict(self): + return dict(type=self.type, offset=self.offset, size=self.cluster_size) + class Qcow2BitmapTable: @@ -39,6 +52,9 @@ class Qcow2BitmapTable: entry.cluster_size)) print("") + def get_info_dict(self): + return dict(table_entries=self.entries) + class Qcow2BitmapDirEntry: @@ -102,6 +118,18 @@ class Qcow2BitmapDirEntry: self.bitmap_table.print_bitmap_table() + def get_info_dict(self): + return dict(name=self.name, + flags=self.bitmap_flags, + flag_bits=self.flag_bits, + bitmap_table_offset=self.bitmap_table_offset, + bitmap_table_size=self.bitmap_table_size, + type=self.type, + granularity=self.granularity_bits, + name_size=self.name_size, + extra_data_size=self.extra_data_size, + bitmap_table=self.bitmap_table) + class Qcow2BitmapDirectory: @@ -177,6 +205,31 @@ class Qcow2BitmapExt: self.dump_bitmap_ext() self.dump_bitmap_directory() + def get_info_dict(self): + return dict(nb_bitmaps=self.nb_bitmaps, + reserved32=self.reserved32, + bitmap_directory_size=self.bitmap_directory_size, + bitmap_directory_offset=self.bitmap_directory_offset, + entries=self.bitmaps) + + +class Qcow2HeaderDoc: + + def __init__(self, h): + self.header = h + + def get_info_dict(self): + return dict(QCOW2_header=self.header) + + +class Qcow2HeaderExtensionsDoc: + + def __init__(self, extensions): + self.extensions = extensions + + def get_info_dict(self): + return dict(QCOW2_header_extensions=self.extensions) + class QcowHeaderExtension: @@ -224,6 +277,17 @@ class QcowHeaderExtension: if self.obj is not None: self.obj.load(fd) + def get_info_dict(self): + he_dict = dict(Header_extension=self.name, + magic=hex(self.magic), + length=self.length) + if self.obj is not None: + he_dict.update(data=self.obj) + else: + he_dict.update(data_str=self.data_str) + + return he_dict + class QcowHeader: @@ -353,9 +417,34 @@ class QcowHeader: print("%-25s" % f[2], value_str) print("") + def get_info_dict(self): + return dict(magic=hex(self.magic), + version=self.version, + backing_file_offset=hex(self.backing_file_offset), + backing_file_size=self.backing_file_size, + cluster_bits=self.cluster_bits, + size=self.size, + crypt_method=self.crypt_method, + l1_size=self.l1_size, + l1_table_offset=hex(self.l1_table_offset), + refcount_table_offset=hex(self.refcount_table_offset), + refcount_table_clusters=self.refcount_table_clusters, + nb_snapshots=self.nb_snapshots, + snapshot_offset=hex(self.snapshot_offset), + incompatible_features=self.incompatible_features, + compatible_features=self.compatible_features, + autoclear_features=self.autoclear_features, + refcount_order=self.refcount_order, + header_length=self.header_length) + def dump_extensions(self): - for ex in self.extensions: + if dump_json: + ext_doc = Qcow2HeaderExtensionsDoc(self.extensions) + print(json.dumps(ext_doc.get_info_dict(), indent=4, + cls=ComplexEncoder)) + return + for ex in self.extensions: print("%-25s %s" % ("Header extension:", ex.name)) print("%-25s %#x" % ("magic", ex.magic)) print("%-25s %d" % ("length", ex.length)) @@ -368,7 +457,11 @@ class QcowHeader: def cmd_dump_header(fd): h = QcowHeader(fd) - h.dump() + if dump_json: + h_doc = Qcow2HeaderDoc(h) + print(json.dumps(h_doc.get_info_dict(), indent=4, cls=ComplexEncoder)) + else: + h.dump() h.dump_extensions() def cmd_dump_header_exts(fd): @@ -460,6 +553,12 @@ cmds = [ ] def main(filename, cmd, args): + global dump_json + dump_json = '-j' in sys.argv + if dump_json: + sys.argv.remove('-j') + args.remove('-j') + fd = open(filename, "r+b") try: for name, handler, num_args, desc in cmds: @@ -476,11 +575,14 @@ def main(filename, cmd, args): fd.close() def usage(): - print("Usage: %s <file> <cmd> [<arg>, ...]" % sys.argv[0]) + print("Usage: %s <file> <cmd> [<arg>, ...] [<key>, ...]" % sys.argv[0]) print("") print("Supported commands:") for name, handler, num_args, desc in cmds: print(" %-20s - %s" % (name, desc)) + print("") + print("Supported keys:") + print(" %-20s - %s" % ('-j', 'Dump in JSON format')) if __name__ == '__main__': if len(sys.argv) < 3: -- 1.8.3.1