1d5262c71SVladimir Sementsov-Ogievskiy# Library for manipulations with qcow2 image 2d5262c71SVladimir Sementsov-Ogievskiy# 30903e3b3SVladimir Sementsov-Ogievskiy# Copyright (c) 2020 Virtuozzo International GmbH. 43419ec71SEric Blake# Copyright (C) 2012 Red Hat, Inc. 50903e3b3SVladimir Sementsov-Ogievskiy# 6d5262c71SVladimir Sementsov-Ogievskiy# This program is free software; you can redistribute it and/or modify 7d5262c71SVladimir Sementsov-Ogievskiy# it under the terms of the GNU General Public License as published by 8d5262c71SVladimir Sementsov-Ogievskiy# the Free Software Foundation; either version 2 of the License, or 9d5262c71SVladimir Sementsov-Ogievskiy# (at your option) any later version. 10d5262c71SVladimir Sementsov-Ogievskiy# 11d5262c71SVladimir Sementsov-Ogievskiy# This program is distributed in the hope that it will be useful, 12d5262c71SVladimir Sementsov-Ogievskiy# but WITHOUT ANY WARRANTY; without even the implied warranty of 13d5262c71SVladimir Sementsov-Ogievskiy# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14d5262c71SVladimir Sementsov-Ogievskiy# GNU General Public License for more details. 15d5262c71SVladimir Sementsov-Ogievskiy# 16d5262c71SVladimir Sementsov-Ogievskiy# You should have received a copy of the GNU General Public License 17d5262c71SVladimir Sementsov-Ogievskiy# along with this program. If not, see <http://www.gnu.org/licenses/>. 18d5262c71SVladimir Sementsov-Ogievskiy# 19d5262c71SVladimir Sementsov-Ogievskiy 20d5262c71SVladimir Sementsov-Ogievskiyimport struct 21d5262c71SVladimir Sementsov-Ogievskiyimport string 22*4edcca57SAndrey Shinkevichimport json 23*4edcca57SAndrey Shinkevich 24*4edcca57SAndrey Shinkevich 25*4edcca57SAndrey Shinkevichclass ComplexEncoder(json.JSONEncoder): 26*4edcca57SAndrey Shinkevich def default(self, obj): 27*4edcca57SAndrey Shinkevich if hasattr(obj, 'to_json'): 28*4edcca57SAndrey Shinkevich return obj.to_json() 29*4edcca57SAndrey Shinkevich else: 30*4edcca57SAndrey Shinkevich return json.JSONEncoder.default(self, obj) 31d5262c71SVladimir Sementsov-Ogievskiy 32d5262c71SVladimir Sementsov-Ogievskiy 33860543f0SVladimir Sementsov-Ogievskiyclass Qcow2Field: 34860543f0SVladimir Sementsov-Ogievskiy 35860543f0SVladimir Sementsov-Ogievskiy def __init__(self, value): 36860543f0SVladimir Sementsov-Ogievskiy self.value = value 37860543f0SVladimir Sementsov-Ogievskiy 38860543f0SVladimir Sementsov-Ogievskiy def __str__(self): 39860543f0SVladimir Sementsov-Ogievskiy return str(self.value) 40860543f0SVladimir Sementsov-Ogievskiy 41860543f0SVladimir Sementsov-Ogievskiy 42860543f0SVladimir Sementsov-Ogievskiyclass Flags64(Qcow2Field): 43860543f0SVladimir Sementsov-Ogievskiy 44860543f0SVladimir Sementsov-Ogievskiy def __str__(self): 45860543f0SVladimir Sementsov-Ogievskiy bits = [] 46860543f0SVladimir Sementsov-Ogievskiy for bit in range(64): 47860543f0SVladimir Sementsov-Ogievskiy if self.value & (1 << bit): 48860543f0SVladimir Sementsov-Ogievskiy bits.append(bit) 49860543f0SVladimir Sementsov-Ogievskiy return str(bits) 50860543f0SVladimir Sementsov-Ogievskiy 51860543f0SVladimir Sementsov-Ogievskiy 5282cb8223SAndrey Shinkevichclass BitmapFlags(Qcow2Field): 5382cb8223SAndrey Shinkevich 5482cb8223SAndrey Shinkevich flags = { 5582cb8223SAndrey Shinkevich 0x1: 'in-use', 5682cb8223SAndrey Shinkevich 0x2: 'auto' 5782cb8223SAndrey Shinkevich } 5882cb8223SAndrey Shinkevich 5982cb8223SAndrey Shinkevich def __str__(self): 6082cb8223SAndrey Shinkevich bits = [] 6182cb8223SAndrey Shinkevich for bit in range(64): 6282cb8223SAndrey Shinkevich flag = self.value & (1 << bit) 6382cb8223SAndrey Shinkevich if flag: 6482cb8223SAndrey Shinkevich bits.append(self.flags.get(flag, f'bit-{bit}')) 6582cb8223SAndrey Shinkevich return f'{self.value:#x} ({bits})' 6682cb8223SAndrey Shinkevich 6782cb8223SAndrey Shinkevich 68aef87784SVladimir Sementsov-Ogievskiyclass Enum(Qcow2Field): 69aef87784SVladimir Sementsov-Ogievskiy 70aef87784SVladimir Sementsov-Ogievskiy def __str__(self): 71aef87784SVladimir Sementsov-Ogievskiy return f'{self.value:#x} ({self.mapping.get(self.value, "<unknown>")})' 72aef87784SVladimir Sementsov-Ogievskiy 73aef87784SVladimir Sementsov-Ogievskiy 740903e3b3SVladimir Sementsov-Ogievskiyclass Qcow2StructMeta(type): 750903e3b3SVladimir Sementsov-Ogievskiy 760903e3b3SVladimir Sementsov-Ogievskiy # Mapping from c types to python struct format 770903e3b3SVladimir Sementsov-Ogievskiy ctypes = { 780903e3b3SVladimir Sementsov-Ogievskiy 'u8': 'B', 790903e3b3SVladimir Sementsov-Ogievskiy 'u16': 'H', 800903e3b3SVladimir Sementsov-Ogievskiy 'u32': 'I', 810903e3b3SVladimir Sementsov-Ogievskiy 'u64': 'Q' 820903e3b3SVladimir Sementsov-Ogievskiy } 830903e3b3SVladimir Sementsov-Ogievskiy 840903e3b3SVladimir Sementsov-Ogievskiy def __init__(self, name, bases, attrs): 850903e3b3SVladimir Sementsov-Ogievskiy if 'fields' in attrs: 860903e3b3SVladimir Sementsov-Ogievskiy self.fmt = '>' + ''.join(self.ctypes[f[0]] for f in self.fields) 870903e3b3SVladimir Sementsov-Ogievskiy 880903e3b3SVladimir Sementsov-Ogievskiy 890903e3b3SVladimir Sementsov-Ogievskiyclass Qcow2Struct(metaclass=Qcow2StructMeta): 900903e3b3SVladimir Sementsov-Ogievskiy 910903e3b3SVladimir Sementsov-Ogievskiy """Qcow2Struct: base class for qcow2 data structures 920903e3b3SVladimir Sementsov-Ogievskiy 930903e3b3SVladimir Sementsov-Ogievskiy Successors should define fields class variable, which is: list of tuples, 940903e3b3SVladimir Sementsov-Ogievskiy each of three elements: 950903e3b3SVladimir Sementsov-Ogievskiy - c-type (one of 'u8', 'u16', 'u32', 'u64') 960903e3b3SVladimir Sementsov-Ogievskiy - format (format_spec to use with .format() when dump or 'mask' to dump 970903e3b3SVladimir Sementsov-Ogievskiy bitmasks) 980903e3b3SVladimir Sementsov-Ogievskiy - field name 990903e3b3SVladimir Sementsov-Ogievskiy """ 1000903e3b3SVladimir Sementsov-Ogievskiy 1010903e3b3SVladimir Sementsov-Ogievskiy def __init__(self, fd=None, offset=None, data=None): 1020903e3b3SVladimir Sementsov-Ogievskiy """ 1030903e3b3SVladimir Sementsov-Ogievskiy Two variants: 1040903e3b3SVladimir Sementsov-Ogievskiy 1. Specify data. fd and offset must be None. 1050903e3b3SVladimir Sementsov-Ogievskiy 2. Specify fd and offset, data must be None. offset may be omitted 1060903e3b3SVladimir Sementsov-Ogievskiy in this case, than current position of fd is used. 1070903e3b3SVladimir Sementsov-Ogievskiy """ 1080903e3b3SVladimir Sementsov-Ogievskiy if data is None: 1090903e3b3SVladimir Sementsov-Ogievskiy assert fd is not None 1100903e3b3SVladimir Sementsov-Ogievskiy buf_size = struct.calcsize(self.fmt) 1110903e3b3SVladimir Sementsov-Ogievskiy if offset is not None: 1120903e3b3SVladimir Sementsov-Ogievskiy fd.seek(offset) 1130903e3b3SVladimir Sementsov-Ogievskiy data = fd.read(buf_size) 1140903e3b3SVladimir Sementsov-Ogievskiy else: 1150903e3b3SVladimir Sementsov-Ogievskiy assert fd is None and offset is None 1160903e3b3SVladimir Sementsov-Ogievskiy 1170903e3b3SVladimir Sementsov-Ogievskiy values = struct.unpack(self.fmt, data) 1180903e3b3SVladimir Sementsov-Ogievskiy self.__dict__ = dict((field[2], values[i]) 1190903e3b3SVladimir Sementsov-Ogievskiy for i, field in enumerate(self.fields)) 1200903e3b3SVladimir Sementsov-Ogievskiy 1212c6d9ca4SAndrey Shinkevich def dump(self, is_json=False): 122*4edcca57SAndrey Shinkevich if is_json: 123*4edcca57SAndrey Shinkevich print(json.dumps(self.to_json(), indent=4, cls=ComplexEncoder)) 124*4edcca57SAndrey Shinkevich return 125*4edcca57SAndrey Shinkevich 1260903e3b3SVladimir Sementsov-Ogievskiy for f in self.fields: 1270903e3b3SVladimir Sementsov-Ogievskiy value = self.__dict__[f[2]] 128860543f0SVladimir Sementsov-Ogievskiy if isinstance(f[1], str): 1290903e3b3SVladimir Sementsov-Ogievskiy value_str = f[1].format(value) 130860543f0SVladimir Sementsov-Ogievskiy else: 131860543f0SVladimir Sementsov-Ogievskiy value_str = str(f[1](value)) 1320903e3b3SVladimir Sementsov-Ogievskiy 1330903e3b3SVladimir Sementsov-Ogievskiy print('{:<25} {}'.format(f[2], value_str)) 1340903e3b3SVladimir Sementsov-Ogievskiy 135b4e92779SAndrey Shinkevich def to_json(self): 136b4e92779SAndrey Shinkevich return dict((f[2], self.__dict__[f[2]]) for f in self.fields) 137b4e92779SAndrey Shinkevich 1380903e3b3SVladimir Sementsov-Ogievskiy 139820c6beeSVladimir Sementsov-Ogievskiyclass Qcow2BitmapExt(Qcow2Struct): 140820c6beeSVladimir Sementsov-Ogievskiy 141820c6beeSVladimir Sementsov-Ogievskiy fields = ( 142820c6beeSVladimir Sementsov-Ogievskiy ('u32', '{}', 'nb_bitmaps'), 143820c6beeSVladimir Sementsov-Ogievskiy ('u32', '{}', 'reserved32'), 144820c6beeSVladimir Sementsov-Ogievskiy ('u64', '{:#x}', 'bitmap_directory_size'), 145820c6beeSVladimir Sementsov-Ogievskiy ('u64', '{:#x}', 'bitmap_directory_offset') 146820c6beeSVladimir Sementsov-Ogievskiy ) 147820c6beeSVladimir Sementsov-Ogievskiy 148e3f5aad7SAndrey Shinkevich def __init__(self, fd, cluster_size): 149991a02caSAndrey Shinkevich super().__init__(fd=fd) 150991a02caSAndrey Shinkevich tail = struct.calcsize(self.fmt) % 8 151991a02caSAndrey Shinkevich if tail: 152991a02caSAndrey Shinkevich fd.seek(8 - tail, 1) 15311173931SAndrey Shinkevich position = fd.tell() 154e3f5aad7SAndrey Shinkevich self.cluster_size = cluster_size 15511173931SAndrey Shinkevich self.read_bitmap_directory(fd) 15611173931SAndrey Shinkevich fd.seek(position) 15711173931SAndrey Shinkevich 15811173931SAndrey Shinkevich def read_bitmap_directory(self, fd): 15911173931SAndrey Shinkevich fd.seek(self.bitmap_directory_offset) 16011173931SAndrey Shinkevich self.bitmap_directory = \ 161e3f5aad7SAndrey Shinkevich [Qcow2BitmapDirEntry(fd, cluster_size=self.cluster_size) 162e3f5aad7SAndrey Shinkevich for _ in range(self.nb_bitmaps)] 16311173931SAndrey Shinkevich 16411173931SAndrey Shinkevich def dump(self): 16511173931SAndrey Shinkevich super().dump() 16611173931SAndrey Shinkevich for entry in self.bitmap_directory: 16711173931SAndrey Shinkevich print() 16811173931SAndrey Shinkevich entry.dump() 16911173931SAndrey Shinkevich 170b4e92779SAndrey Shinkevich def to_json(self): 171b4e92779SAndrey Shinkevich fields_dict = super().to_json() 172b4e92779SAndrey Shinkevich fields_dict['bitmap_directory'] = self.bitmap_directory 173b4e92779SAndrey Shinkevich return fields_dict 174b4e92779SAndrey Shinkevich 17511173931SAndrey Shinkevich 17611173931SAndrey Shinkevichclass Qcow2BitmapDirEntry(Qcow2Struct): 17711173931SAndrey Shinkevich 17811173931SAndrey Shinkevich fields = ( 17911173931SAndrey Shinkevich ('u64', '{:#x}', 'bitmap_table_offset'), 18011173931SAndrey Shinkevich ('u32', '{}', 'bitmap_table_size'), 18111173931SAndrey Shinkevich ('u32', BitmapFlags, 'flags'), 18211173931SAndrey Shinkevich ('u8', '{}', 'type'), 18311173931SAndrey Shinkevich ('u8', '{}', 'granularity_bits'), 18411173931SAndrey Shinkevich ('u16', '{}', 'name_size'), 18511173931SAndrey Shinkevich ('u32', '{}', 'extra_data_size') 18611173931SAndrey Shinkevich ) 18711173931SAndrey Shinkevich 188e3f5aad7SAndrey Shinkevich def __init__(self, fd, cluster_size): 18911173931SAndrey Shinkevich super().__init__(fd=fd) 190e3f5aad7SAndrey Shinkevich self.cluster_size = cluster_size 19111173931SAndrey Shinkevich # Seek relative to the current position in the file 19211173931SAndrey Shinkevich fd.seek(self.extra_data_size, 1) 19311173931SAndrey Shinkevich bitmap_name = fd.read(self.name_size) 19411173931SAndrey Shinkevich self.name = bitmap_name.decode('ascii') 19511173931SAndrey Shinkevich # Move position to the end of the entry in the directory 19611173931SAndrey Shinkevich entry_raw_size = self.bitmap_dir_entry_raw_size() 19711173931SAndrey Shinkevich padding = ((entry_raw_size + 7) & ~7) - entry_raw_size 19811173931SAndrey Shinkevich fd.seek(padding, 1) 19994277841SAndrey Shinkevich self.bitmap_table = Qcow2BitmapTable(fd=fd, 20094277841SAndrey Shinkevich offset=self.bitmap_table_offset, 20194277841SAndrey Shinkevich nb_entries=self.bitmap_table_size, 20294277841SAndrey Shinkevich cluster_size=self.cluster_size) 20311173931SAndrey Shinkevich 20411173931SAndrey Shinkevich def bitmap_dir_entry_raw_size(self): 20511173931SAndrey Shinkevich return struct.calcsize(self.fmt) + self.name_size + \ 20611173931SAndrey Shinkevich self.extra_data_size 20711173931SAndrey Shinkevich 20811173931SAndrey Shinkevich def dump(self): 20911173931SAndrey Shinkevich print(f'{"Bitmap name":<25} {self.name}') 21011173931SAndrey Shinkevich super(Qcow2BitmapDirEntry, self).dump() 21194277841SAndrey Shinkevich self.bitmap_table.dump() 21294277841SAndrey Shinkevich 213b4e92779SAndrey Shinkevich def to_json(self): 214b4e92779SAndrey Shinkevich # Put the name ahead of the dict 215b4e92779SAndrey Shinkevich return { 216b4e92779SAndrey Shinkevich 'name': self.name, 217b4e92779SAndrey Shinkevich **super().to_json(), 218b4e92779SAndrey Shinkevich 'bitmap_table': self.bitmap_table 219b4e92779SAndrey Shinkevich } 220b4e92779SAndrey Shinkevich 22194277841SAndrey Shinkevich 22294277841SAndrey Shinkevichclass Qcow2BitmapTableEntry(Qcow2Struct): 22394277841SAndrey Shinkevich 22494277841SAndrey Shinkevich fields = ( 22594277841SAndrey Shinkevich ('u64', '{}', 'entry'), 22694277841SAndrey Shinkevich ) 22794277841SAndrey Shinkevich 22894277841SAndrey Shinkevich BME_TABLE_ENTRY_RESERVED_MASK = 0xff000000000001fe 22994277841SAndrey Shinkevich BME_TABLE_ENTRY_OFFSET_MASK = 0x00fffffffffffe00 23094277841SAndrey Shinkevich BME_TABLE_ENTRY_FLAG_ALL_ONES = 1 23194277841SAndrey Shinkevich 23294277841SAndrey Shinkevich def __init__(self, fd): 23394277841SAndrey Shinkevich super().__init__(fd=fd) 23494277841SAndrey Shinkevich self.reserved = self.entry & self.BME_TABLE_ENTRY_RESERVED_MASK 23594277841SAndrey Shinkevich self.offset = self.entry & self.BME_TABLE_ENTRY_OFFSET_MASK 23694277841SAndrey Shinkevich if self.offset: 23794277841SAndrey Shinkevich if self.entry & self.BME_TABLE_ENTRY_FLAG_ALL_ONES: 23894277841SAndrey Shinkevich self.type = 'invalid' 23994277841SAndrey Shinkevich else: 24094277841SAndrey Shinkevich self.type = 'serialized' 24194277841SAndrey Shinkevich elif self.entry & self.BME_TABLE_ENTRY_FLAG_ALL_ONES: 24294277841SAndrey Shinkevich self.type = 'all-ones' 24394277841SAndrey Shinkevich else: 24494277841SAndrey Shinkevich self.type = 'all-zeroes' 24594277841SAndrey Shinkevich 246b4e92779SAndrey Shinkevich def to_json(self): 247b4e92779SAndrey Shinkevich return {'type': self.type, 'offset': self.offset, 248b4e92779SAndrey Shinkevich 'reserved': self.reserved} 249b4e92779SAndrey Shinkevich 25094277841SAndrey Shinkevich 25194277841SAndrey Shinkevichclass Qcow2BitmapTable: 25294277841SAndrey Shinkevich 25394277841SAndrey Shinkevich def __init__(self, fd, offset, nb_entries, cluster_size): 25494277841SAndrey Shinkevich self.cluster_size = cluster_size 25594277841SAndrey Shinkevich position = fd.tell() 25694277841SAndrey Shinkevich fd.seek(offset) 25794277841SAndrey Shinkevich self.entries = [Qcow2BitmapTableEntry(fd) for _ in range(nb_entries)] 25894277841SAndrey Shinkevich fd.seek(position) 25994277841SAndrey Shinkevich 26094277841SAndrey Shinkevich def dump(self): 26194277841SAndrey Shinkevich bitmap_table = enumerate(self.entries) 26294277841SAndrey Shinkevich print(f'{"Bitmap table":<14} {"type":<15} {"size":<12} {"offset"}') 26394277841SAndrey Shinkevich for i, entry in bitmap_table: 26494277841SAndrey Shinkevich if entry.type == 'serialized': 26594277841SAndrey Shinkevich size = self.cluster_size 26694277841SAndrey Shinkevich else: 26794277841SAndrey Shinkevich size = 0 26894277841SAndrey Shinkevich print(f'{i:<14} {entry.type:<15} {size:<12} {entry.offset}') 26911173931SAndrey Shinkevich 270b4e92779SAndrey Shinkevich def to_json(self): 271b4e92779SAndrey Shinkevich return self.entries 272b4e92779SAndrey Shinkevich 273820c6beeSVladimir Sementsov-Ogievskiy 274820c6beeSVladimir Sementsov-OgievskiyQCOW2_EXT_MAGIC_BITMAPS = 0x23852875 275820c6beeSVladimir Sementsov-Ogievskiy 276820c6beeSVladimir Sementsov-Ogievskiy 277a9e750e1SVladimir Sementsov-Ogievskiyclass QcowHeaderExtension(Qcow2Struct): 278d5262c71SVladimir Sementsov-Ogievskiy 279aef87784SVladimir Sementsov-Ogievskiy class Magic(Enum): 280aef87784SVladimir Sementsov-Ogievskiy mapping = { 281aef87784SVladimir Sementsov-Ogievskiy 0xe2792aca: 'Backing format', 282aef87784SVladimir Sementsov-Ogievskiy 0x6803f857: 'Feature table', 283aef87784SVladimir Sementsov-Ogievskiy 0x0537be77: 'Crypto header', 284820c6beeSVladimir Sementsov-Ogievskiy QCOW2_EXT_MAGIC_BITMAPS: 'Bitmaps', 285aef87784SVladimir Sementsov-Ogievskiy 0x44415441: 'Data file' 286aef87784SVladimir Sementsov-Ogievskiy } 287aef87784SVladimir Sementsov-Ogievskiy 288b4e92779SAndrey Shinkevich def to_json(self): 289b4e92779SAndrey Shinkevich return self.mapping.get(self.value, "<unknown>") 290b4e92779SAndrey Shinkevich 291a9e750e1SVladimir Sementsov-Ogievskiy fields = ( 292aef87784SVladimir Sementsov-Ogievskiy ('u32', Magic, 'magic'), 293a9e750e1SVladimir Sementsov-Ogievskiy ('u32', '{}', 'length') 294a9e750e1SVladimir Sementsov-Ogievskiy # length bytes of data follows 295a9e750e1SVladimir Sementsov-Ogievskiy # then padding to next multiply of 8 296a9e750e1SVladimir Sementsov-Ogievskiy ) 297a9e750e1SVladimir Sementsov-Ogievskiy 298e3f5aad7SAndrey Shinkevich def __init__(self, magic=None, length=None, data=None, fd=None, 299e3f5aad7SAndrey Shinkevich cluster_size=None): 300a9e750e1SVladimir Sementsov-Ogievskiy """ 301a9e750e1SVladimir Sementsov-Ogievskiy Support both loading from fd and creation from user data. 302a9e750e1SVladimir Sementsov-Ogievskiy For fd-based creation current position in a file will be used to read 303a9e750e1SVladimir Sementsov-Ogievskiy the data. 304e3f5aad7SAndrey Shinkevich The cluster_size value may be obtained by dependent structures. 305a9e750e1SVladimir Sementsov-Ogievskiy 306a9e750e1SVladimir Sementsov-Ogievskiy This should be somehow refactored and functionality should be moved to 307a9e750e1SVladimir Sementsov-Ogievskiy superclass (to allow creation of any qcow2 struct), but then, fields 308a9e750e1SVladimir Sementsov-Ogievskiy of variable length (data here) should be supported in base class 309820c6beeSVladimir Sementsov-Ogievskiy somehow. Note also, that we probably want to parse different 310820c6beeSVladimir Sementsov-Ogievskiy extensions. Should they be subclasses of this class, or how to do it 311820c6beeSVladimir Sementsov-Ogievskiy better? Should it be something like QAPI union with discriminator field 312820c6beeSVladimir Sementsov-Ogievskiy (magic here). So, it's a TODO. We'll see how to properly refactor this 313820c6beeSVladimir Sementsov-Ogievskiy when we have more qcow2 structures. 314a9e750e1SVladimir Sementsov-Ogievskiy """ 315a9e750e1SVladimir Sementsov-Ogievskiy if fd is None: 316a9e750e1SVladimir Sementsov-Ogievskiy assert all(v is not None for v in (magic, length, data)) 317a9e750e1SVladimir Sementsov-Ogievskiy self.magic = magic 318a9e750e1SVladimir Sementsov-Ogievskiy self.length = length 319d5262c71SVladimir Sementsov-Ogievskiy if length % 8 != 0: 320d5262c71SVladimir Sementsov-Ogievskiy padding = 8 - (length % 8) 321621ca498SVladimir Sementsov-Ogievskiy data += b'\0' * padding 322d5262c71SVladimir Sementsov-Ogievskiy self.data = data 323a9e750e1SVladimir Sementsov-Ogievskiy else: 324a9e750e1SVladimir Sementsov-Ogievskiy assert all(v is None for v in (magic, length, data)) 325a9e750e1SVladimir Sementsov-Ogievskiy super().__init__(fd=fd) 326991a02caSAndrey Shinkevich if self.magic == QCOW2_EXT_MAGIC_BITMAPS: 327e3f5aad7SAndrey Shinkevich self.obj = Qcow2BitmapExt(fd=fd, cluster_size=cluster_size) 328991a02caSAndrey Shinkevich self.data = None 329991a02caSAndrey Shinkevich else: 330a9e750e1SVladimir Sementsov-Ogievskiy padded = (self.length + 7) & ~7 331a9e750e1SVladimir Sementsov-Ogievskiy self.data = fd.read(padded) 332a9e750e1SVladimir Sementsov-Ogievskiy assert self.data is not None 333991a02caSAndrey Shinkevich self.obj = None 334d5262c71SVladimir Sementsov-Ogievskiy 335991a02caSAndrey Shinkevich if self.data is not None: 3364539b364SAndrey Shinkevich data_str = self.data[:self.length] 337991a02caSAndrey Shinkevich if all(c in string.printable.encode( 338991a02caSAndrey Shinkevich 'ascii') for c in data_str): 3394539b364SAndrey Shinkevich data_str = f"'{ data_str.decode('ascii') }'" 3404539b364SAndrey Shinkevich else: 3414539b364SAndrey Shinkevich data_str = '<binary>' 3424539b364SAndrey Shinkevich self.data_str = data_str 3434539b364SAndrey Shinkevich 344820c6beeSVladimir Sementsov-Ogievskiy 3450931fcc7SVladimir Sementsov-Ogievskiy def dump(self): 346820c6beeSVladimir Sementsov-Ogievskiy super().dump() 347820c6beeSVladimir Sementsov-Ogievskiy 348820c6beeSVladimir Sementsov-Ogievskiy if self.obj is None: 3494539b364SAndrey Shinkevich print(f'{"data":<25} {self.data_str}') 350820c6beeSVladimir Sementsov-Ogievskiy else: 351820c6beeSVladimir Sementsov-Ogievskiy self.obj.dump() 3520931fcc7SVladimir Sementsov-Ogievskiy 353b4e92779SAndrey Shinkevich def to_json(self): 354b4e92779SAndrey Shinkevich # Put the name ahead of the dict 355b4e92779SAndrey Shinkevich res = {'name': self.Magic(self.magic), **super().to_json()} 356b4e92779SAndrey Shinkevich if self.obj is not None: 357b4e92779SAndrey Shinkevich res['data'] = self.obj 358b4e92779SAndrey Shinkevich else: 359b4e92779SAndrey Shinkevich res['data_str'] = self.data_str 360b4e92779SAndrey Shinkevich 361b4e92779SAndrey Shinkevich return res 362b4e92779SAndrey Shinkevich 363d5262c71SVladimir Sementsov-Ogievskiy @classmethod 364d5262c71SVladimir Sementsov-Ogievskiy def create(cls, magic, data): 365d5262c71SVladimir Sementsov-Ogievskiy return QcowHeaderExtension(magic, len(data), data) 366d5262c71SVladimir Sementsov-Ogievskiy 367d5262c71SVladimir Sementsov-Ogievskiy 3680903e3b3SVladimir Sementsov-Ogievskiyclass QcowHeader(Qcow2Struct): 369d5262c71SVladimir Sementsov-Ogievskiy 370b2f14154SVladimir Sementsov-Ogievskiy fields = ( 371d5262c71SVladimir Sementsov-Ogievskiy # Version 2 header fields 3725432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{:#x}', 'magic'), 3735432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'version'), 3745432a0dbSVladimir Sementsov-Ogievskiy ('u64', '{:#x}', 'backing_file_offset'), 3755432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{:#x}', 'backing_file_size'), 3765432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'cluster_bits'), 3775432a0dbSVladimir Sementsov-Ogievskiy ('u64', '{}', 'size'), 3785432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'crypt_method'), 3795432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'l1_size'), 3805432a0dbSVladimir Sementsov-Ogievskiy ('u64', '{:#x}', 'l1_table_offset'), 3815432a0dbSVladimir Sementsov-Ogievskiy ('u64', '{:#x}', 'refcount_table_offset'), 3825432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'refcount_table_clusters'), 3835432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'nb_snapshots'), 3845432a0dbSVladimir Sementsov-Ogievskiy ('u64', '{:#x}', 'snapshot_offset'), 385d5262c71SVladimir Sementsov-Ogievskiy 386d5262c71SVladimir Sementsov-Ogievskiy # Version 3 header fields 387860543f0SVladimir Sementsov-Ogievskiy ('u64', Flags64, 'incompatible_features'), 388860543f0SVladimir Sementsov-Ogievskiy ('u64', Flags64, 'compatible_features'), 389860543f0SVladimir Sementsov-Ogievskiy ('u64', Flags64, 'autoclear_features'), 3905432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'refcount_order'), 3915432a0dbSVladimir Sementsov-Ogievskiy ('u32', '{}', 'header_length'), 392b2f14154SVladimir Sementsov-Ogievskiy ) 393d5262c71SVladimir Sementsov-Ogievskiy 394d5262c71SVladimir Sementsov-Ogievskiy def __init__(self, fd): 3950903e3b3SVladimir Sementsov-Ogievskiy super().__init__(fd=fd, offset=0) 396d5262c71SVladimir Sementsov-Ogievskiy 397d5262c71SVladimir Sementsov-Ogievskiy self.set_defaults() 398d5262c71SVladimir Sementsov-Ogievskiy self.cluster_size = 1 << self.cluster_bits 399d5262c71SVladimir Sementsov-Ogievskiy 400d5262c71SVladimir Sementsov-Ogievskiy fd.seek(self.header_length) 401d5262c71SVladimir Sementsov-Ogievskiy self.load_extensions(fd) 402d5262c71SVladimir Sementsov-Ogievskiy 403d5262c71SVladimir Sementsov-Ogievskiy if self.backing_file_offset: 404d5262c71SVladimir Sementsov-Ogievskiy fd.seek(self.backing_file_offset) 405d5262c71SVladimir Sementsov-Ogievskiy self.backing_file = fd.read(self.backing_file_size) 406d5262c71SVladimir Sementsov-Ogievskiy else: 407d5262c71SVladimir Sementsov-Ogievskiy self.backing_file = None 408d5262c71SVladimir Sementsov-Ogievskiy 409d5262c71SVladimir Sementsov-Ogievskiy def set_defaults(self): 410d5262c71SVladimir Sementsov-Ogievskiy if self.version == 2: 411d5262c71SVladimir Sementsov-Ogievskiy self.incompatible_features = 0 412d5262c71SVladimir Sementsov-Ogievskiy self.compatible_features = 0 413d5262c71SVladimir Sementsov-Ogievskiy self.autoclear_features = 0 414d5262c71SVladimir Sementsov-Ogievskiy self.refcount_order = 4 415d5262c71SVladimir Sementsov-Ogievskiy self.header_length = 72 416d5262c71SVladimir Sementsov-Ogievskiy 417d5262c71SVladimir Sementsov-Ogievskiy def load_extensions(self, fd): 418d5262c71SVladimir Sementsov-Ogievskiy self.extensions = [] 419d5262c71SVladimir Sementsov-Ogievskiy 420d5262c71SVladimir Sementsov-Ogievskiy if self.backing_file_offset != 0: 421d5262c71SVladimir Sementsov-Ogievskiy end = min(self.cluster_size, self.backing_file_offset) 422d5262c71SVladimir Sementsov-Ogievskiy else: 423d5262c71SVladimir Sementsov-Ogievskiy end = self.cluster_size 424d5262c71SVladimir Sementsov-Ogievskiy 425d5262c71SVladimir Sementsov-Ogievskiy while fd.tell() < end: 426e3f5aad7SAndrey Shinkevich ext = QcowHeaderExtension(fd=fd, cluster_size=self.cluster_size) 427a9e750e1SVladimir Sementsov-Ogievskiy if ext.magic == 0: 428d5262c71SVladimir Sementsov-Ogievskiy break 429d5262c71SVladimir Sementsov-Ogievskiy else: 430a9e750e1SVladimir Sementsov-Ogievskiy self.extensions.append(ext) 431d5262c71SVladimir Sementsov-Ogievskiy 432d5262c71SVladimir Sementsov-Ogievskiy def update_extensions(self, fd): 433d5262c71SVladimir Sementsov-Ogievskiy 434d5262c71SVladimir Sementsov-Ogievskiy fd.seek(self.header_length) 435d5262c71SVladimir Sementsov-Ogievskiy extensions = self.extensions 436621ca498SVladimir Sementsov-Ogievskiy extensions.append(QcowHeaderExtension(0, 0, b'')) 437d5262c71SVladimir Sementsov-Ogievskiy for ex in extensions: 438d5262c71SVladimir Sementsov-Ogievskiy buf = struct.pack('>II', ex.magic, ex.length) 439d5262c71SVladimir Sementsov-Ogievskiy fd.write(buf) 440d5262c71SVladimir Sementsov-Ogievskiy fd.write(ex.data) 441d5262c71SVladimir Sementsov-Ogievskiy 442d5262c71SVladimir Sementsov-Ogievskiy if self.backing_file is not None: 443d5262c71SVladimir Sementsov-Ogievskiy self.backing_file_offset = fd.tell() 444d5262c71SVladimir Sementsov-Ogievskiy fd.write(self.backing_file) 445d5262c71SVladimir Sementsov-Ogievskiy 446d5262c71SVladimir Sementsov-Ogievskiy if fd.tell() > self.cluster_size: 447621ca498SVladimir Sementsov-Ogievskiy raise Exception('I think I just broke the image...') 448d5262c71SVladimir Sementsov-Ogievskiy 449d5262c71SVladimir Sementsov-Ogievskiy def update(self, fd): 450d5262c71SVladimir Sementsov-Ogievskiy header_bytes = self.header_length 451d5262c71SVladimir Sementsov-Ogievskiy 452d5262c71SVladimir Sementsov-Ogievskiy self.update_extensions(fd) 453d5262c71SVladimir Sementsov-Ogievskiy 454d5262c71SVladimir Sementsov-Ogievskiy fd.seek(0) 455d5262c71SVladimir Sementsov-Ogievskiy header = tuple(self.__dict__[f] for t, p, f in QcowHeader.fields) 456d5262c71SVladimir Sementsov-Ogievskiy buf = struct.pack(QcowHeader.fmt, *header) 457d5262c71SVladimir Sementsov-Ogievskiy buf = buf[0:header_bytes-1] 458d5262c71SVladimir Sementsov-Ogievskiy fd.write(buf) 459d5262c71SVladimir Sementsov-Ogievskiy 4602c6d9ca4SAndrey Shinkevich def dump_extensions(self, is_json=False): 461*4edcca57SAndrey Shinkevich if is_json: 462*4edcca57SAndrey Shinkevich print(json.dumps(self.extensions, indent=4, cls=ComplexEncoder)) 463*4edcca57SAndrey Shinkevich return 464*4edcca57SAndrey Shinkevich 465d5262c71SVladimir Sementsov-Ogievskiy for ex in self.extensions: 466621ca498SVladimir Sementsov-Ogievskiy print('Header extension:') 4670931fcc7SVladimir Sementsov-Ogievskiy ex.dump() 468621ca498SVladimir Sementsov-Ogievskiy print() 469