1*6e19b3c4SKevin Wolf#!/usr/bin/env python 2*6e19b3c4SKevin Wolf 3*6e19b3c4SKevin Wolfimport sys 4*6e19b3c4SKevin Wolfimport struct 5*6e19b3c4SKevin Wolfimport string 6*6e19b3c4SKevin Wolf 7*6e19b3c4SKevin Wolfclass QcowHeaderExtension: 8*6e19b3c4SKevin Wolf 9*6e19b3c4SKevin Wolf def __init__(self, magic, length, data): 10*6e19b3c4SKevin Wolf self.magic = magic 11*6e19b3c4SKevin Wolf self.length = length 12*6e19b3c4SKevin Wolf self.data = data 13*6e19b3c4SKevin Wolf 14*6e19b3c4SKevin Wolf @classmethod 15*6e19b3c4SKevin Wolf def create(cls, magic, data): 16*6e19b3c4SKevin Wolf return QcowHeaderExtension(magic, len(data), data) 17*6e19b3c4SKevin Wolf 18*6e19b3c4SKevin Wolfclass QcowHeader: 19*6e19b3c4SKevin Wolf 20*6e19b3c4SKevin Wolf uint32_t = 'I' 21*6e19b3c4SKevin Wolf uint64_t = 'Q' 22*6e19b3c4SKevin Wolf 23*6e19b3c4SKevin Wolf fields = [ 24*6e19b3c4SKevin Wolf # Version 2 header fields 25*6e19b3c4SKevin Wolf [ uint32_t, '%#x', 'magic' ], 26*6e19b3c4SKevin Wolf [ uint32_t, '%d', 'version' ], 27*6e19b3c4SKevin Wolf [ uint64_t, '%#x', 'backing_file_offset' ], 28*6e19b3c4SKevin Wolf [ uint32_t, '%#x', 'backing_file_size' ], 29*6e19b3c4SKevin Wolf [ uint32_t, '%d', 'cluster_bits' ], 30*6e19b3c4SKevin Wolf [ uint64_t, '%d', 'size' ], 31*6e19b3c4SKevin Wolf [ uint32_t, '%d', 'crypt_method' ], 32*6e19b3c4SKevin Wolf [ uint32_t, '%d', 'l1_size' ], 33*6e19b3c4SKevin Wolf [ uint64_t, '%#x', 'l1_table_offset' ], 34*6e19b3c4SKevin Wolf [ uint64_t, '%#x', 'refcount_table_offset' ], 35*6e19b3c4SKevin Wolf [ uint32_t, '%d', 'refcount_table_clusters' ], 36*6e19b3c4SKevin Wolf [ uint32_t, '%d', 'nb_snapshots' ], 37*6e19b3c4SKevin Wolf [ uint64_t, '%#x', 'snapshot_offset' ], 38*6e19b3c4SKevin Wolf ]; 39*6e19b3c4SKevin Wolf 40*6e19b3c4SKevin Wolf fmt = '>' + ''.join(field[0] for field in fields) 41*6e19b3c4SKevin Wolf 42*6e19b3c4SKevin Wolf def __init__(self, fd): 43*6e19b3c4SKevin Wolf 44*6e19b3c4SKevin Wolf buf_size = struct.calcsize(QcowHeader.fmt) 45*6e19b3c4SKevin Wolf 46*6e19b3c4SKevin Wolf fd.seek(0) 47*6e19b3c4SKevin Wolf buf = fd.read(buf_size) 48*6e19b3c4SKevin Wolf 49*6e19b3c4SKevin Wolf header = struct.unpack(QcowHeader.fmt, buf) 50*6e19b3c4SKevin Wolf self.__dict__ = dict((field[2], header[i]) 51*6e19b3c4SKevin Wolf for i, field in enumerate(QcowHeader.fields)) 52*6e19b3c4SKevin Wolf 53*6e19b3c4SKevin Wolf self.cluster_size = 1 << self.cluster_bits 54*6e19b3c4SKevin Wolf 55*6e19b3c4SKevin Wolf fd.seek(self.get_header_length()) 56*6e19b3c4SKevin Wolf self.load_extensions(fd) 57*6e19b3c4SKevin Wolf 58*6e19b3c4SKevin Wolf if self.backing_file_offset: 59*6e19b3c4SKevin Wolf fd.seek(self.backing_file_offset) 60*6e19b3c4SKevin Wolf self.backing_file = fd.read(self.backing_file_size) 61*6e19b3c4SKevin Wolf else: 62*6e19b3c4SKevin Wolf self.backing_file = None 63*6e19b3c4SKevin Wolf 64*6e19b3c4SKevin Wolf def get_header_length(self): 65*6e19b3c4SKevin Wolf if self.version == 2: 66*6e19b3c4SKevin Wolf return 72 67*6e19b3c4SKevin Wolf else: 68*6e19b3c4SKevin Wolf raise Exception("version != 2 not supported") 69*6e19b3c4SKevin Wolf 70*6e19b3c4SKevin Wolf def load_extensions(self, fd): 71*6e19b3c4SKevin Wolf self.extensions = [] 72*6e19b3c4SKevin Wolf 73*6e19b3c4SKevin Wolf if self.backing_file_offset != 0: 74*6e19b3c4SKevin Wolf end = min(self.cluster_size, self.backing_file_offset) 75*6e19b3c4SKevin Wolf else: 76*6e19b3c4SKevin Wolf end = self.cluster_size 77*6e19b3c4SKevin Wolf 78*6e19b3c4SKevin Wolf while fd.tell() < end: 79*6e19b3c4SKevin Wolf (magic, length) = struct.unpack('>II', fd.read(8)) 80*6e19b3c4SKevin Wolf if magic == 0: 81*6e19b3c4SKevin Wolf break 82*6e19b3c4SKevin Wolf else: 83*6e19b3c4SKevin Wolf padded = (length + 7) & ~7 84*6e19b3c4SKevin Wolf data = fd.read(padded) 85*6e19b3c4SKevin Wolf self.extensions.append(QcowHeaderExtension(magic, length, data)) 86*6e19b3c4SKevin Wolf 87*6e19b3c4SKevin Wolf def update_extensions(self, fd): 88*6e19b3c4SKevin Wolf 89*6e19b3c4SKevin Wolf fd.seek(self.get_header_length()) 90*6e19b3c4SKevin Wolf extensions = self.extensions 91*6e19b3c4SKevin Wolf extensions.append(QcowHeaderExtension(0, 0, "")) 92*6e19b3c4SKevin Wolf for ex in extensions: 93*6e19b3c4SKevin Wolf buf = struct.pack('>II', ex.magic, ex.length) 94*6e19b3c4SKevin Wolf fd.write(buf) 95*6e19b3c4SKevin Wolf fd.write(ex.data) 96*6e19b3c4SKevin Wolf 97*6e19b3c4SKevin Wolf if self.backing_file != None: 98*6e19b3c4SKevin Wolf self.backing_file_offset = fd.tell() 99*6e19b3c4SKevin Wolf fd.write(self.backing_file) 100*6e19b3c4SKevin Wolf 101*6e19b3c4SKevin Wolf if fd.tell() > self.cluster_size: 102*6e19b3c4SKevin Wolf raise Exception("I think I just broke the image...") 103*6e19b3c4SKevin Wolf 104*6e19b3c4SKevin Wolf 105*6e19b3c4SKevin Wolf def update(self, fd): 106*6e19b3c4SKevin Wolf header_bytes = self.get_header_length() 107*6e19b3c4SKevin Wolf 108*6e19b3c4SKevin Wolf self.update_extensions(fd) 109*6e19b3c4SKevin Wolf 110*6e19b3c4SKevin Wolf fd.seek(0) 111*6e19b3c4SKevin Wolf header = tuple(self.__dict__[f] for t, p, f in QcowHeader.fields) 112*6e19b3c4SKevin Wolf buf = struct.pack(QcowHeader.fmt, *header) 113*6e19b3c4SKevin Wolf buf = buf[0:header_bytes-1] 114*6e19b3c4SKevin Wolf fd.write(buf) 115*6e19b3c4SKevin Wolf 116*6e19b3c4SKevin Wolf def dump(self): 117*6e19b3c4SKevin Wolf for f in QcowHeader.fields: 118*6e19b3c4SKevin Wolf print "%-25s" % f[2], f[1] % self.__dict__[f[2]] 119*6e19b3c4SKevin Wolf print "" 120*6e19b3c4SKevin Wolf 121*6e19b3c4SKevin Wolf def dump_extensions(self): 122*6e19b3c4SKevin Wolf for ex in self.extensions: 123*6e19b3c4SKevin Wolf 124*6e19b3c4SKevin Wolf data = ex.data[:ex.length] 125*6e19b3c4SKevin Wolf if all(c in string.printable for c in data): 126*6e19b3c4SKevin Wolf data = "'%s'" % data 127*6e19b3c4SKevin Wolf else: 128*6e19b3c4SKevin Wolf data = "<binary>" 129*6e19b3c4SKevin Wolf 130*6e19b3c4SKevin Wolf print "Header extension:" 131*6e19b3c4SKevin Wolf print "%-25s %#x" % ("magic", ex.magic) 132*6e19b3c4SKevin Wolf print "%-25s %d" % ("length", ex.length) 133*6e19b3c4SKevin Wolf print "%-25s %s" % ("data", data) 134*6e19b3c4SKevin Wolf print "" 135*6e19b3c4SKevin Wolf 136*6e19b3c4SKevin Wolf 137*6e19b3c4SKevin Wolfdef cmd_dump_header(fd): 138*6e19b3c4SKevin Wolf h = QcowHeader(fd) 139*6e19b3c4SKevin Wolf h.dump() 140*6e19b3c4SKevin Wolf h.dump_extensions() 141*6e19b3c4SKevin Wolf 142*6e19b3c4SKevin Wolfdef cmd_add_header_ext(fd, magic, data): 143*6e19b3c4SKevin Wolf try: 144*6e19b3c4SKevin Wolf magic = int(magic, 0) 145*6e19b3c4SKevin Wolf except: 146*6e19b3c4SKevin Wolf print "'%s' is not a valid magic number" % magic 147*6e19b3c4SKevin Wolf sys.exit(1) 148*6e19b3c4SKevin Wolf 149*6e19b3c4SKevin Wolf h = QcowHeader(fd) 150*6e19b3c4SKevin Wolf h.extensions.append(QcowHeaderExtension.create(magic, data)) 151*6e19b3c4SKevin Wolf h.update(fd) 152*6e19b3c4SKevin Wolf 153*6e19b3c4SKevin Wolfdef cmd_del_header_ext(fd, magic): 154*6e19b3c4SKevin Wolf try: 155*6e19b3c4SKevin Wolf magic = int(magic, 0) 156*6e19b3c4SKevin Wolf except: 157*6e19b3c4SKevin Wolf print "'%s' is not a valid magic number" % magic 158*6e19b3c4SKevin Wolf sys.exit(1) 159*6e19b3c4SKevin Wolf 160*6e19b3c4SKevin Wolf h = QcowHeader(fd) 161*6e19b3c4SKevin Wolf found = False 162*6e19b3c4SKevin Wolf 163*6e19b3c4SKevin Wolf for ex in h.extensions: 164*6e19b3c4SKevin Wolf if ex.magic == magic: 165*6e19b3c4SKevin Wolf found = True 166*6e19b3c4SKevin Wolf h.extensions.remove(ex) 167*6e19b3c4SKevin Wolf 168*6e19b3c4SKevin Wolf if not found: 169*6e19b3c4SKevin Wolf print "No such header extension" 170*6e19b3c4SKevin Wolf return 171*6e19b3c4SKevin Wolf 172*6e19b3c4SKevin Wolf h.update(fd) 173*6e19b3c4SKevin Wolf 174*6e19b3c4SKevin Wolfcmds = [ 175*6e19b3c4SKevin Wolf [ 'dump-header', cmd_dump_header, 0, 'Dump image header and header extensions' ], 176*6e19b3c4SKevin Wolf [ 'add-header-ext', cmd_add_header_ext, 2, 'Add a header extension' ], 177*6e19b3c4SKevin Wolf [ 'del-header-ext', cmd_del_header_ext, 1, 'Delete a header extension' ], 178*6e19b3c4SKevin Wolf] 179*6e19b3c4SKevin Wolf 180*6e19b3c4SKevin Wolfdef main(filename, cmd, args): 181*6e19b3c4SKevin Wolf fd = open(filename, "r+b") 182*6e19b3c4SKevin Wolf try: 183*6e19b3c4SKevin Wolf for name, handler, num_args, desc in cmds: 184*6e19b3c4SKevin Wolf if name != cmd: 185*6e19b3c4SKevin Wolf continue 186*6e19b3c4SKevin Wolf elif len(args) != num_args: 187*6e19b3c4SKevin Wolf usage() 188*6e19b3c4SKevin Wolf return 189*6e19b3c4SKevin Wolf else: 190*6e19b3c4SKevin Wolf handler(fd, *args) 191*6e19b3c4SKevin Wolf return 192*6e19b3c4SKevin Wolf print "Unknown command '%s'" % cmd 193*6e19b3c4SKevin Wolf finally: 194*6e19b3c4SKevin Wolf fd.close() 195*6e19b3c4SKevin Wolf 196*6e19b3c4SKevin Wolfdef usage(): 197*6e19b3c4SKevin Wolf print "Usage: %s <file> <cmd> [<arg>, ...]" % sys.argv[0] 198*6e19b3c4SKevin Wolf print "" 199*6e19b3c4SKevin Wolf print "Supported commands:" 200*6e19b3c4SKevin Wolf for name, handler, num_args, desc in cmds: 201*6e19b3c4SKevin Wolf print " %-20s - %s" % (name, desc) 202*6e19b3c4SKevin Wolf 203*6e19b3c4SKevin Wolfif len(sys.argv) < 3: 204*6e19b3c4SKevin Wolf usage() 205*6e19b3c4SKevin Wolf sys.exit(1) 206*6e19b3c4SKevin Wolf 207*6e19b3c4SKevin Wolfmain(sys.argv[1], sys.argv[2], sys.argv[3:]) 208