1*903cb1bfSPhilippe Mathieu-Daudé#!/usr/bin/env python3 2e77964f7SStefan Hajnoczi# 3e77964f7SStefan Hajnoczi# Tool to manipulate QED image files 4e77964f7SStefan Hajnoczi# 5e77964f7SStefan Hajnoczi# Copyright (C) 2010 IBM, Corp. 6e77964f7SStefan Hajnoczi# 7e77964f7SStefan Hajnoczi# Authors: 8e77964f7SStefan Hajnoczi# Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> 9e77964f7SStefan Hajnoczi# 10e77964f7SStefan Hajnoczi# This work is licensed under the terms of the GNU GPL, version 2 or later. 11e77964f7SStefan Hajnoczi# See the COPYING file in the top-level directory. 12e77964f7SStefan Hajnoczi 13e77964f7SStefan Hajnocziimport sys 14e77964f7SStefan Hajnocziimport struct 15e77964f7SStefan Hajnocziimport random 16e77964f7SStefan Hajnocziimport optparse 17e77964f7SStefan Hajnoczi 18e77964f7SStefan Hajnoczi# This can be used as a module 19e77964f7SStefan Hajnoczi__all__ = ['QED_F_NEED_CHECK', 'QED'] 20e77964f7SStefan Hajnoczi 21e77964f7SStefan HajnocziQED_F_NEED_CHECK = 0x02 22e77964f7SStefan Hajnoczi 23e77964f7SStefan Hajnocziheader_fmt = '<IIIIQQQQQII' 24e77964f7SStefan Hajnocziheader_size = struct.calcsize(header_fmt) 25e77964f7SStefan Hajnoczifield_names = ['magic', 'cluster_size', 'table_size', 26e77964f7SStefan Hajnoczi 'header_size', 'features', 'compat_features', 27e77964f7SStefan Hajnoczi 'autoclear_features', 'l1_table_offset', 'image_size', 28e77964f7SStefan Hajnoczi 'backing_filename_offset', 'backing_filename_size'] 29e77964f7SStefan Hajnoczitable_elem_fmt = '<Q' 30e77964f7SStefan Hajnoczitable_elem_size = struct.calcsize(table_elem_fmt) 31e77964f7SStefan Hajnoczi 32e77964f7SStefan Hajnoczidef err(msg): 33e77964f7SStefan Hajnoczi sys.stderr.write(msg + '\n') 34e77964f7SStefan Hajnoczi sys.exit(1) 35e77964f7SStefan Hajnoczi 36e77964f7SStefan Hajnoczidef unpack_header(s): 37e77964f7SStefan Hajnoczi fields = struct.unpack(header_fmt, s) 38e77964f7SStefan Hajnoczi return dict((field_names[idx], val) for idx, val in enumerate(fields)) 39e77964f7SStefan Hajnoczi 40e77964f7SStefan Hajnoczidef pack_header(header): 41e77964f7SStefan Hajnoczi fields = tuple(header[x] for x in field_names) 42e77964f7SStefan Hajnoczi return struct.pack(header_fmt, *fields) 43e77964f7SStefan Hajnoczi 44e77964f7SStefan Hajnoczidef unpack_table_elem(s): 45e77964f7SStefan Hajnoczi return struct.unpack(table_elem_fmt, s)[0] 46e77964f7SStefan Hajnoczi 47e77964f7SStefan Hajnoczidef pack_table_elem(elem): 48e77964f7SStefan Hajnoczi return struct.pack(table_elem_fmt, elem) 49e77964f7SStefan Hajnoczi 50e77964f7SStefan Hajnocziclass QED(object): 51e77964f7SStefan Hajnoczi def __init__(self, f): 52e77964f7SStefan Hajnoczi self.f = f 53e77964f7SStefan Hajnoczi 54e77964f7SStefan Hajnoczi self.f.seek(0, 2) 55e77964f7SStefan Hajnoczi self.filesize = f.tell() 56e77964f7SStefan Hajnoczi 57e77964f7SStefan Hajnoczi self.load_header() 58e77964f7SStefan Hajnoczi self.load_l1_table() 59e77964f7SStefan Hajnoczi 60e77964f7SStefan Hajnoczi def raw_pread(self, offset, size): 61e77964f7SStefan Hajnoczi self.f.seek(offset) 62e77964f7SStefan Hajnoczi return self.f.read(size) 63e77964f7SStefan Hajnoczi 64e77964f7SStefan Hajnoczi def raw_pwrite(self, offset, data): 65e77964f7SStefan Hajnoczi self.f.seek(offset) 66e77964f7SStefan Hajnoczi return self.f.write(data) 67e77964f7SStefan Hajnoczi 68e77964f7SStefan Hajnoczi def load_header(self): 69e77964f7SStefan Hajnoczi self.header = unpack_header(self.raw_pread(0, header_size)) 70e77964f7SStefan Hajnoczi 71e77964f7SStefan Hajnoczi def store_header(self): 72e77964f7SStefan Hajnoczi self.raw_pwrite(0, pack_header(self.header)) 73e77964f7SStefan Hajnoczi 74e77964f7SStefan Hajnoczi def read_table(self, offset): 75e77964f7SStefan Hajnoczi size = self.header['table_size'] * self.header['cluster_size'] 76e77964f7SStefan Hajnoczi s = self.raw_pread(offset, size) 77e77964f7SStefan Hajnoczi table = [unpack_table_elem(s[i:i + table_elem_size]) for i in xrange(0, size, table_elem_size)] 78e77964f7SStefan Hajnoczi return table 79e77964f7SStefan Hajnoczi 80e77964f7SStefan Hajnoczi def load_l1_table(self): 81e77964f7SStefan Hajnoczi self.l1_table = self.read_table(self.header['l1_table_offset']) 829a3a9a63SMax Reitz self.table_nelems = self.header['table_size'] * self.header['cluster_size'] // table_elem_size 83e77964f7SStefan Hajnoczi 84e77964f7SStefan Hajnoczi def write_table(self, offset, table): 85e77964f7SStefan Hajnoczi s = ''.join(pack_table_elem(x) for x in table) 86e77964f7SStefan Hajnoczi self.raw_pwrite(offset, s) 87e77964f7SStefan Hajnoczi 88e77964f7SStefan Hajnoczidef random_table_item(table): 89e77964f7SStefan Hajnoczi vals = [(index, offset) for index, offset in enumerate(table) if offset != 0] 90e77964f7SStefan Hajnoczi if not vals: 91e77964f7SStefan Hajnoczi err('cannot pick random item because table is empty') 92e77964f7SStefan Hajnoczi return random.choice(vals) 93e77964f7SStefan Hajnoczi 94e77964f7SStefan Hajnoczidef corrupt_table_duplicate(table): 95e77964f7SStefan Hajnoczi '''Corrupt a table by introducing a duplicate offset''' 96e77964f7SStefan Hajnoczi victim_idx, victim_val = random_table_item(table) 97e77964f7SStefan Hajnoczi unique_vals = set(table) 98e77964f7SStefan Hajnoczi if len(unique_vals) == 1: 99e77964f7SStefan Hajnoczi err('no duplication corruption possible in table') 100e77964f7SStefan Hajnoczi dup_val = random.choice(list(unique_vals.difference([victim_val]))) 101e77964f7SStefan Hajnoczi table[victim_idx] = dup_val 102e77964f7SStefan Hajnoczi 103e77964f7SStefan Hajnoczidef corrupt_table_invalidate(qed, table): 104e77964f7SStefan Hajnoczi '''Corrupt a table by introducing an invalid offset''' 105e77964f7SStefan Hajnoczi index, _ = random_table_item(table) 106e77964f7SStefan Hajnoczi table[index] = qed.filesize + random.randint(0, 100 * 1024 * 1024 * 1024 * 1024) 107e77964f7SStefan Hajnoczi 108e77964f7SStefan Hajnoczidef cmd_show(qed, *args): 109e77964f7SStefan Hajnoczi '''show [header|l1|l2 <offset>]- Show header or l1/l2 tables''' 110e77964f7SStefan Hajnoczi if not args or args[0] == 'header': 111f03868bdSEduardo Habkost print(qed.header) 112e77964f7SStefan Hajnoczi elif args[0] == 'l1': 113f03868bdSEduardo Habkost print(qed.l1_table) 114e77964f7SStefan Hajnoczi elif len(args) == 2 and args[0] == 'l2': 115e77964f7SStefan Hajnoczi offset = int(args[1]) 116f03868bdSEduardo Habkost print(qed.read_table(offset)) 117e77964f7SStefan Hajnoczi else: 118e77964f7SStefan Hajnoczi err('unrecognized sub-command') 119e77964f7SStefan Hajnoczi 120e77964f7SStefan Hajnoczidef cmd_duplicate(qed, table_level): 121e77964f7SStefan Hajnoczi '''duplicate l1|l2 - Duplicate a random table element''' 122e77964f7SStefan Hajnoczi if table_level == 'l1': 123e77964f7SStefan Hajnoczi offset = qed.header['l1_table_offset'] 124e77964f7SStefan Hajnoczi table = qed.l1_table 125e77964f7SStefan Hajnoczi elif table_level == 'l2': 126e77964f7SStefan Hajnoczi _, offset = random_table_item(qed.l1_table) 127e77964f7SStefan Hajnoczi table = qed.read_table(offset) 128e77964f7SStefan Hajnoczi else: 129e77964f7SStefan Hajnoczi err('unrecognized sub-command') 130e77964f7SStefan Hajnoczi corrupt_table_duplicate(table) 131e77964f7SStefan Hajnoczi qed.write_table(offset, table) 132e77964f7SStefan Hajnoczi 133e77964f7SStefan Hajnoczidef cmd_invalidate(qed, table_level): 134e77964f7SStefan Hajnoczi '''invalidate l1|l2 - Plant an invalid table element at random''' 135e77964f7SStefan Hajnoczi if table_level == 'l1': 136e77964f7SStefan Hajnoczi offset = qed.header['l1_table_offset'] 137e77964f7SStefan Hajnoczi table = qed.l1_table 138e77964f7SStefan Hajnoczi elif table_level == 'l2': 139e77964f7SStefan Hajnoczi _, offset = random_table_item(qed.l1_table) 140e77964f7SStefan Hajnoczi table = qed.read_table(offset) 141e77964f7SStefan Hajnoczi else: 142e77964f7SStefan Hajnoczi err('unrecognized sub-command') 143e77964f7SStefan Hajnoczi corrupt_table_invalidate(qed, table) 144e77964f7SStefan Hajnoczi qed.write_table(offset, table) 145e77964f7SStefan Hajnoczi 146e77964f7SStefan Hajnoczidef cmd_need_check(qed, *args): 147e77964f7SStefan Hajnoczi '''need-check [on|off] - Test, set, or clear the QED_F_NEED_CHECK header bit''' 148e77964f7SStefan Hajnoczi if not args: 149f03868bdSEduardo Habkost print(bool(qed.header['features'] & QED_F_NEED_CHECK)) 150e77964f7SStefan Hajnoczi return 151e77964f7SStefan Hajnoczi 152e77964f7SStefan Hajnoczi if args[0] == 'on': 153e77964f7SStefan Hajnoczi qed.header['features'] |= QED_F_NEED_CHECK 154e77964f7SStefan Hajnoczi elif args[0] == 'off': 155e77964f7SStefan Hajnoczi qed.header['features'] &= ~QED_F_NEED_CHECK 156e77964f7SStefan Hajnoczi else: 157e77964f7SStefan Hajnoczi err('unrecognized sub-command') 158e77964f7SStefan Hajnoczi qed.store_header() 159e77964f7SStefan Hajnoczi 160e77964f7SStefan Hajnoczidef cmd_zero_cluster(qed, pos, *args): 161e77964f7SStefan Hajnoczi '''zero-cluster <pos> [<n>] - Zero data clusters''' 162e77964f7SStefan Hajnoczi pos, n = int(pos), 1 163e77964f7SStefan Hajnoczi if args: 164e77964f7SStefan Hajnoczi if len(args) != 1: 165e77964f7SStefan Hajnoczi err('expected one argument') 166e77964f7SStefan Hajnoczi n = int(args[0]) 167e77964f7SStefan Hajnoczi 168e77964f7SStefan Hajnoczi for i in xrange(n): 1699a3a9a63SMax Reitz l1_index = pos // qed.header['cluster_size'] // len(qed.l1_table) 170e77964f7SStefan Hajnoczi if qed.l1_table[l1_index] == 0: 171e77964f7SStefan Hajnoczi err('no l2 table allocated') 172e77964f7SStefan Hajnoczi 173e77964f7SStefan Hajnoczi l2_offset = qed.l1_table[l1_index] 174e77964f7SStefan Hajnoczi l2_table = qed.read_table(l2_offset) 175e77964f7SStefan Hajnoczi 1769a3a9a63SMax Reitz l2_index = (pos // qed.header['cluster_size']) % len(qed.l1_table) 177e77964f7SStefan Hajnoczi l2_table[l2_index] = 1 # zero the data cluster 178e77964f7SStefan Hajnoczi qed.write_table(l2_offset, l2_table) 179e77964f7SStefan Hajnoczi pos += qed.header['cluster_size'] 180e77964f7SStefan Hajnoczi 181e77964f7SStefan Hajnoczidef cmd_copy_metadata(qed, outfile): 182e77964f7SStefan Hajnoczi '''copy-metadata <outfile> - Copy metadata only (for scrubbing corrupted images)''' 183e77964f7SStefan Hajnoczi out = open(outfile, 'wb') 184e77964f7SStefan Hajnoczi 185e77964f7SStefan Hajnoczi # Match file size 186e77964f7SStefan Hajnoczi out.seek(qed.filesize - 1) 187e77964f7SStefan Hajnoczi out.write('\0') 188e77964f7SStefan Hajnoczi 189e77964f7SStefan Hajnoczi # Copy header clusters 190e77964f7SStefan Hajnoczi out.seek(0) 191e77964f7SStefan Hajnoczi header_size_bytes = qed.header['header_size'] * qed.header['cluster_size'] 192e77964f7SStefan Hajnoczi out.write(qed.raw_pread(0, header_size_bytes)) 193e77964f7SStefan Hajnoczi 194e77964f7SStefan Hajnoczi # Copy L1 table 195e77964f7SStefan Hajnoczi out.seek(qed.header['l1_table_offset']) 196e77964f7SStefan Hajnoczi s = ''.join(pack_table_elem(x) for x in qed.l1_table) 197e77964f7SStefan Hajnoczi out.write(s) 198e77964f7SStefan Hajnoczi 199e77964f7SStefan Hajnoczi # Copy L2 tables 200e77964f7SStefan Hajnoczi for l2_offset in qed.l1_table: 201e77964f7SStefan Hajnoczi if l2_offset == 0: 202e77964f7SStefan Hajnoczi continue 203e77964f7SStefan Hajnoczi l2_table = qed.read_table(l2_offset) 204e77964f7SStefan Hajnoczi out.seek(l2_offset) 205e77964f7SStefan Hajnoczi s = ''.join(pack_table_elem(x) for x in l2_table) 206e77964f7SStefan Hajnoczi out.write(s) 207e77964f7SStefan Hajnoczi 208e77964f7SStefan Hajnoczi out.close() 209e77964f7SStefan Hajnoczi 210e77964f7SStefan Hajnoczidef usage(): 211f03868bdSEduardo Habkost print('Usage: %s <file> <cmd> [<arg>, ...]' % sys.argv[0]) 212f03868bdSEduardo Habkost print() 213f03868bdSEduardo Habkost print('Supported commands:') 214e77964f7SStefan Hajnoczi for cmd in sorted(x for x in globals() if x.startswith('cmd_')): 215f03868bdSEduardo Habkost print(globals()[cmd].__doc__) 216e77964f7SStefan Hajnoczi sys.exit(1) 217e77964f7SStefan Hajnoczi 218e77964f7SStefan Hajnoczidef main(): 219e77964f7SStefan Hajnoczi if len(sys.argv) < 3: 220e77964f7SStefan Hajnoczi usage() 221e77964f7SStefan Hajnoczi filename, cmd = sys.argv[1:3] 222e77964f7SStefan Hajnoczi 223e77964f7SStefan Hajnoczi cmd = 'cmd_' + cmd.replace('-', '_') 224e77964f7SStefan Hajnoczi if cmd not in globals(): 225e77964f7SStefan Hajnoczi usage() 226e77964f7SStefan Hajnoczi 227e77964f7SStefan Hajnoczi qed = QED(open(filename, 'r+b')) 228e77964f7SStefan Hajnoczi try: 229e77964f7SStefan Hajnoczi globals()[cmd](qed, *sys.argv[3:]) 23003e18810SMarkus Armbruster except TypeError as e: 231e77964f7SStefan Hajnoczi sys.stderr.write(globals()[cmd].__doc__ + '\n') 232e77964f7SStefan Hajnoczi sys.exit(1) 233e77964f7SStefan Hajnoczi 234e77964f7SStefan Hajnocziif __name__ == '__main__': 235e77964f7SStefan Hajnoczi main() 236