Hi,
I just wrote a small python script that helps resizing a QCow2 image
without rewriting it.
I'm sure that's not the correct way to do it, but it seems to work well.
Beware this script DO NOT SUPPORT SHRINKING !! It do not check any error
case, so; use at your own risks !
--
Laurent Coustet
http://ed.zehome.com/
#!/usr/bin/env python
## (c) 2009 Laurent Coustet
# Clarisys Informatique
#
# Script to resize a qcow2 qemu image
import struct
import decimal
qcow2header = struct.Struct(">IIQIIQIIQQIIQ") # Big Endian
def sizeof_fmt(num):
bak = num
for x in ['bytes','KB','MB','GB','TB']:
if num < 1024.0:
return "%d %3.1f%s" % (bak, num, x)
num /= 1024.0
class QCow2Header(object):
def __init__(self, *args):
print "args: %s" % (args,)
self.magic = args[0]
self.version = args[1]
self.backing_file_offset = args[2]
self.backing_file_size = args[3]
self.cluster_bits = args[4]
self.size = args[5]
self.crypt_method = args[6]
self.l1_size = args[7]
self.l1_table_offset = args[8]
self.refcount_table_offset = args[9]
self.refcount_table_clusters = args[10]
self.nb_snapshots = args[11]
self.snapshots_offset = args[12]
def __str__(self):
return "Qcow2Header %s version %s size: %s" % (self.magic, self.version, sizeof_fmt(self.size))
def check(self):
'Q' 'F' 'I' '\xfb'
return bool((ord('Q') << 24 | ord('F') << 16 | (ord('I') << 8) | ord('\xfb') << 0) == self.magic)
def resize(self, newsize):
"""
@params newsize: size in bytes K for Kilobytes M for Megabytes G for Gigabytes
Exemple 200 = 200 Bytes
200M = 200 * 1024 * 1024 Bytes
2G = 2 * 1024 * 1024 * 1024 Bytes
+50M = (currentSize + (50 * 1024 * 1024)) Bytes
"""
add = False
newsize = newsize.upper()
# Extract "+"
if newsize[0] == "+":
add = True
newsize = newsize[1:]
# Extract unit
units = {"B": 1, "K": 1024,"M": 1024 ** 2, "G": 1024**3}
unit = "B"
for u in units.keys():
if newsize[-1] == u:
unit = u
newsize = newsize[:-1]
break
newsize_int = int(newsize) * units[unit]
if add:
newsize_int += self.size
print "New size: %s Old size: %s" % (sizeof_fmt(newsize_int), sizeof_fmt(self.size))
self.size = newsize_int
# Recalculate the number l1 size
# LC: Uggly method
size_mega = int(self.size / (1024 ** 2))
self.l1_size = int(decimal.Decimal(decimal.Decimal(size_mega) / decimal.Decimal(512)).quantize(decimal.Decimal('1.'), rounding=decimal.ROUND_UP))
print "l1_size: %s" % (self.l1_size,)
def get(self):
return qcow2header.pack(self.magic, self.version, self.backing_file_offset, self.backing_file_size, self.cluster_bits, self.size, self.crypt_method, self.l1_size, self.l1_table_offset, self.refcount_table_offset, self.refcount_table_clusters, self.nb_snapshots, self.snapshots_offset)
def help():
print """QCow2 Image resizer (grow only)
(c) 2009 Laurent Coustet <e...@zehome.com>
Usage: %s file new_size_in_bytes
"""
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
help()
sys.exit(0)
f = open(sys.argv[1], "rb")
data = f.read(qcow2header.size)
f.close()
if len(data) < qcow2header.size:
print "Invalid header. Not a qcow2 file. Must be %s long." % (qcow2header.size,)
# Try to use unpack
header = QCow2Header(*qcow2header.unpack(data))
if not header.check():
print "This is not a QCow2 image: magic not found."
sys.exit(0)
# Resize the header
header.resize(sys.argv[2])
# Rewrite the file
f = open(sys.argv[1], "r+b")
f.seek(0, 0)
ret = f.write(header.get())
f.close()