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