qcow2_format.py: use tuples instead of lists for fields
[qemu.git] / tests / qemu-iotests / qcow2_format.py
blobe2f08ed69194b7111823a4765bb461a1c751de9a
1 # Library for manipulations with qcow2 image
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 import struct
18 import string
21 class QcowHeaderExtension:
23 def __init__(self, magic, length, data):
24 if length % 8 != 0:
25 padding = 8 - (length % 8)
26 data += b"\0" * padding
28 self.magic = magic
29 self.length = length
30 self.data = data
32 @classmethod
33 def create(cls, magic, data):
34 return QcowHeaderExtension(magic, len(data), data)
37 class QcowHeader:
39 uint32_t = 'I'
40 uint64_t = 'Q'
42 fields = (
43 # Version 2 header fields
44 (uint32_t, '%#x', 'magic'),
45 (uint32_t, '%d', 'version'),
46 (uint64_t, '%#x', 'backing_file_offset'),
47 (uint32_t, '%#x', 'backing_file_size'),
48 (uint32_t, '%d', 'cluster_bits'),
49 (uint64_t, '%d', 'size'),
50 (uint32_t, '%d', 'crypt_method'),
51 (uint32_t, '%d', 'l1_size'),
52 (uint64_t, '%#x', 'l1_table_offset'),
53 (uint64_t, '%#x', 'refcount_table_offset'),
54 (uint32_t, '%d', 'refcount_table_clusters'),
55 (uint32_t, '%d', 'nb_snapshots'),
56 (uint64_t, '%#x', 'snapshot_offset'),
58 # Version 3 header fields
59 (uint64_t, 'mask', 'incompatible_features'),
60 (uint64_t, 'mask', 'compatible_features'),
61 (uint64_t, 'mask', 'autoclear_features'),
62 (uint32_t, '%d', 'refcount_order'),
63 (uint32_t, '%d', 'header_length'),
66 fmt = '>' + ''.join(field[0] for field in fields)
68 def __init__(self, fd):
70 buf_size = struct.calcsize(QcowHeader.fmt)
72 fd.seek(0)
73 buf = fd.read(buf_size)
75 header = struct.unpack(QcowHeader.fmt, buf)
76 self.__dict__ = dict((field[2], header[i])
77 for i, field in enumerate(QcowHeader.fields))
79 self.set_defaults()
80 self.cluster_size = 1 << self.cluster_bits
82 fd.seek(self.header_length)
83 self.load_extensions(fd)
85 if self.backing_file_offset:
86 fd.seek(self.backing_file_offset)
87 self.backing_file = fd.read(self.backing_file_size)
88 else:
89 self.backing_file = None
91 def set_defaults(self):
92 if self.version == 2:
93 self.incompatible_features = 0
94 self.compatible_features = 0
95 self.autoclear_features = 0
96 self.refcount_order = 4
97 self.header_length = 72
99 def load_extensions(self, fd):
100 self.extensions = []
102 if self.backing_file_offset != 0:
103 end = min(self.cluster_size, self.backing_file_offset)
104 else:
105 end = self.cluster_size
107 while fd.tell() < end:
108 (magic, length) = struct.unpack('>II', fd.read(8))
109 if magic == 0:
110 break
111 else:
112 padded = (length + 7) & ~7
113 data = fd.read(padded)
114 self.extensions.append(QcowHeaderExtension(magic, length,
115 data))
117 def update_extensions(self, fd):
119 fd.seek(self.header_length)
120 extensions = self.extensions
121 extensions.append(QcowHeaderExtension(0, 0, b""))
122 for ex in extensions:
123 buf = struct.pack('>II', ex.magic, ex.length)
124 fd.write(buf)
125 fd.write(ex.data)
127 if self.backing_file is not None:
128 self.backing_file_offset = fd.tell()
129 fd.write(self.backing_file)
131 if fd.tell() > self.cluster_size:
132 raise Exception("I think I just broke the image...")
134 def update(self, fd):
135 header_bytes = self.header_length
137 self.update_extensions(fd)
139 fd.seek(0)
140 header = tuple(self.__dict__[f] for t, p, f in QcowHeader.fields)
141 buf = struct.pack(QcowHeader.fmt, *header)
142 buf = buf[0:header_bytes-1]
143 fd.write(buf)
145 def dump(self):
146 for f in QcowHeader.fields:
147 value = self.__dict__[f[2]]
148 if f[1] == 'mask':
149 bits = []
150 for bit in range(64):
151 if value & (1 << bit):
152 bits.append(bit)
153 value_str = str(bits)
154 else:
155 value_str = f[1] % value
157 print("%-25s" % f[2], value_str)
159 def dump_extensions(self):
160 for ex in self.extensions:
162 data = ex.data[:ex.length]
163 if all(c in string.printable.encode('ascii') for c in data):
164 data = "'%s'" % data.decode('ascii')
165 else:
166 data = "<binary>"
168 print("Header extension:")
169 print("%-25s %#x" % ("magic", ex.magic))
170 print("%-25s %d" % ("length", ex.length))
171 print("%-25s %s" % ("data", data))
172 print("")