Statistics
| Branch: | Revision:

root / tests / qemu-iotests / qed.py @ b53169ea

History | View | Annotate | Download (7 kB)

1
#!/usr/bin/env python
2
#
3
# Tool to manipulate QED image files
4
#
5
# Copyright (C) 2010 IBM, Corp.
6
#
7
# Authors:
8
#  Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
9
#
10
# This work is licensed under the terms of the GNU GPL, version 2 or later.
11
# See the COPYING file in the top-level directory.
12

    
13
import sys
14
import struct
15
import random
16
import optparse
17

    
18
# This can be used as a module
19
__all__ = ['QED_F_NEED_CHECK', 'QED']
20

    
21
QED_F_NEED_CHECK = 0x02
22

    
23
header_fmt = '<IIIIQQQQQII'
24
header_size = struct.calcsize(header_fmt)
25
field_names = ['magic', 'cluster_size', 'table_size',
26
               'header_size', 'features', 'compat_features',
27
               'autoclear_features', 'l1_table_offset', 'image_size',
28
               'backing_filename_offset', 'backing_filename_size']
29
table_elem_fmt = '<Q'
30
table_elem_size = struct.calcsize(table_elem_fmt)
31

    
32
def err(msg):
33
    sys.stderr.write(msg + '\n')
34
    sys.exit(1)
35

    
36
def unpack_header(s):
37
    fields = struct.unpack(header_fmt, s)
38
    return dict((field_names[idx], val) for idx, val in enumerate(fields))
39

    
40
def pack_header(header):
41
    fields = tuple(header[x] for x in field_names)
42
    return struct.pack(header_fmt, *fields)
43

    
44
def unpack_table_elem(s):
45
    return struct.unpack(table_elem_fmt, s)[0]
46

    
47
def pack_table_elem(elem):
48
    return struct.pack(table_elem_fmt, elem)
49

    
50
class QED(object):
51
    def __init__(self, f):
52
        self.f = f
53

    
54
        self.f.seek(0, 2)
55
        self.filesize = f.tell()
56

    
57
        self.load_header()
58
        self.load_l1_table()
59

    
60
    def raw_pread(self, offset, size):
61
        self.f.seek(offset)
62
        return self.f.read(size)
63

    
64
    def raw_pwrite(self, offset, data):
65
        self.f.seek(offset)
66
        return self.f.write(data)
67

    
68
    def load_header(self):
69
        self.header = unpack_header(self.raw_pread(0, header_size))
70

    
71
    def store_header(self):
72
        self.raw_pwrite(0, pack_header(self.header))
73

    
74
    def read_table(self, offset):
75
        size = self.header['table_size'] * self.header['cluster_size']
76
        s = self.raw_pread(offset, size)
77
        table = [unpack_table_elem(s[i:i + table_elem_size]) for i in xrange(0, size, table_elem_size)]
78
        return table
79

    
80
    def load_l1_table(self):
81
        self.l1_table = self.read_table(self.header['l1_table_offset'])
82
        self.table_nelems = self.header['table_size'] * self.header['cluster_size'] / table_elem_size
83

    
84
    def write_table(self, offset, table):
85
        s = ''.join(pack_table_elem(x) for x in table)
86
        self.raw_pwrite(offset, s)
87

    
88
def random_table_item(table):
89
    vals = [(index, offset) for index, offset in enumerate(table) if offset != 0]
90
    if not vals:
91
        err('cannot pick random item because table is empty')
92
    return random.choice(vals)
93

    
94
def corrupt_table_duplicate(table):
95
    '''Corrupt a table by introducing a duplicate offset'''
96
    victim_idx, victim_val = random_table_item(table)
97
    unique_vals = set(table)
98
    if len(unique_vals) == 1:
99
        err('no duplication corruption possible in table')
100
    dup_val = random.choice(list(unique_vals.difference([victim_val])))
101
    table[victim_idx] = dup_val
102

    
103
def corrupt_table_invalidate(qed, table):
104
    '''Corrupt a table by introducing an invalid offset'''
105
    index, _ = random_table_item(table)
106
    table[index] = qed.filesize + random.randint(0, 100 * 1024 * 1024 * 1024 * 1024)
107

    
108
def cmd_show(qed, *args):
109
    '''show [header|l1|l2 <offset>]- Show header or l1/l2 tables'''
110
    if not args or args[0] == 'header':
111
        print qed.header
112
    elif args[0] == 'l1':
113
        print qed.l1_table
114
    elif len(args) == 2 and args[0] == 'l2':
115
        offset = int(args[1])
116
        print qed.read_table(offset)
117
    else:
118
        err('unrecognized sub-command')
119

    
120
def cmd_duplicate(qed, table_level):
121
    '''duplicate l1|l2 - Duplicate a random table element'''
122
    if table_level == 'l1':
123
        offset = qed.header['l1_table_offset']
124
        table = qed.l1_table
125
    elif table_level == 'l2':
126
        _, offset = random_table_item(qed.l1_table)
127
        table = qed.read_table(offset)
128
    else:
129
        err('unrecognized sub-command')
130
    corrupt_table_duplicate(table)
131
    qed.write_table(offset, table)
132

    
133
def cmd_invalidate(qed, table_level):
134
    '''invalidate l1|l2 - Plant an invalid table element at random'''
135
    if table_level == 'l1':
136
        offset = qed.header['l1_table_offset']
137
        table = qed.l1_table
138
    elif table_level == 'l2':
139
        _, offset = random_table_item(qed.l1_table)
140
        table = qed.read_table(offset)
141
    else:
142
        err('unrecognized sub-command')
143
    corrupt_table_invalidate(qed, table)
144
    qed.write_table(offset, table)
145

    
146
def cmd_need_check(qed, *args):
147
    '''need-check [on|off] - Test, set, or clear the QED_F_NEED_CHECK header bit'''
148
    if not args:
149
        print bool(qed.header['features'] & QED_F_NEED_CHECK)
150
        return
151

    
152
    if args[0] == 'on':
153
        qed.header['features'] |= QED_F_NEED_CHECK
154
    elif args[0] == 'off':
155
        qed.header['features'] &= ~QED_F_NEED_CHECK
156
    else:
157
        err('unrecognized sub-command')
158
    qed.store_header()
159

    
160
def cmd_zero_cluster(qed, pos, *args):
161
    '''zero-cluster <pos> [<n>] - Zero data clusters'''
162
    pos, n = int(pos), 1
163
    if args:
164
        if len(args) != 1:
165
            err('expected one argument')
166
        n = int(args[0])
167

    
168
    for i in xrange(n):
169
        l1_index = pos / qed.header['cluster_size'] / len(qed.l1_table)
170
        if qed.l1_table[l1_index] == 0:
171
            err('no l2 table allocated')
172

    
173
        l2_offset = qed.l1_table[l1_index]
174
        l2_table = qed.read_table(l2_offset)
175

    
176
        l2_index = (pos / qed.header['cluster_size']) % len(qed.l1_table)
177
        l2_table[l2_index] = 1 # zero the data cluster
178
        qed.write_table(l2_offset, l2_table)
179
        pos += qed.header['cluster_size']
180

    
181
def cmd_copy_metadata(qed, outfile):
182
    '''copy-metadata <outfile> - Copy metadata only (for scrubbing corrupted images)'''
183
    out = open(outfile, 'wb')
184

    
185
    # Match file size
186
    out.seek(qed.filesize - 1)
187
    out.write('\0')
188

    
189
    # Copy header clusters
190
    out.seek(0)
191
    header_size_bytes = qed.header['header_size'] * qed.header['cluster_size']
192
    out.write(qed.raw_pread(0, header_size_bytes))
193

    
194
    # Copy L1 table
195
    out.seek(qed.header['l1_table_offset'])
196
    s = ''.join(pack_table_elem(x) for x in qed.l1_table)
197
    out.write(s)
198

    
199
    # Copy L2 tables
200
    for l2_offset in qed.l1_table:
201
        if l2_offset == 0:
202
            continue
203
        l2_table = qed.read_table(l2_offset)
204
        out.seek(l2_offset)
205
        s = ''.join(pack_table_elem(x) for x in l2_table)
206
        out.write(s)
207

    
208
    out.close()
209

    
210
def usage():
211
    print 'Usage: %s <file> <cmd> [<arg>, ...]' % sys.argv[0]
212
    print
213
    print 'Supported commands:'
214
    for cmd in sorted(x for x in globals() if x.startswith('cmd_')):
215
        print globals()[cmd].__doc__
216
    sys.exit(1)
217

    
218
def main():
219
    if len(sys.argv) < 3:
220
        usage()
221
    filename, cmd = sys.argv[1:3]
222

    
223
    cmd = 'cmd_' + cmd.replace('-', '_')
224
    if cmd not in globals():
225
        usage()
226

    
227
    qed = QED(open(filename, 'r+b'))
228
    try:
229
        globals()[cmd](qed, *sys.argv[3:])
230
    except TypeError, e:
231
        sys.stderr.write(globals()[cmd].__doc__ + '\n')
232
        sys.exit(1)
233

    
234
if __name__ == '__main__':
235
    main()