01.06.2020 16:48, Andrey Shinkevich wrote:
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),
Why to store string instead of int in qcow2? Better store numbers as numbers.
+ 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)
isn't it mostly __dict__? We definitely want to automate this in base class.
+
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:
--
Best regards,
Vladimir