3 # Tool to manipulate QED image files
5 # Copyright (C) 2010 IBM, Corp.
8 # Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
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.
18 # This can be used as a module
19 __all__
= ['QED_F_NEED_CHECK', 'QED']
21 QED_F_NEED_CHECK
= 0x02
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']
30 table_elem_size
= struct
.calcsize(table_elem_fmt
)
33 sys
.stderr
.write(msg
+ '\n')
37 fields
= struct
.unpack(header_fmt
, s
)
38 return dict((field_names
[idx
], val
) for idx
, val
in enumerate(fields
))
40 def pack_header(header
):
41 fields
= tuple(header
[x
] for x
in field_names
)
42 return struct
.pack(header_fmt
, *fields
)
44 def unpack_table_elem(s
):
45 return struct
.unpack(table_elem_fmt
, s
)[0]
47 def pack_table_elem(elem
):
48 return struct
.pack(table_elem_fmt
, elem
)
51 def __init__(self
, f
):
55 self
.filesize
= f
.tell()
60 def raw_pread(self
, offset
, size
):
62 return self
.f
.read(size
)
64 def raw_pwrite(self
, offset
, data
):
66 return self
.f
.write(data
)
68 def load_header(self
):
69 self
.header
= unpack_header(self
.raw_pread(0, header_size
))
71 def store_header(self
):
72 self
.raw_pwrite(0, pack_header(self
.header
))
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
)]
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
84 def write_table(self
, offset
, table
):
85 s
= ''.join(pack_table_elem(x
) for x
in table
)
86 self
.raw_pwrite(offset
, s
)
88 def random_table_item(table
):
89 vals
= [(index
, offset
) for index
, offset
in enumerate(table
) if offset
!= 0]
91 err('cannot pick random item because table is empty')
92 return random
.choice(vals
)
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
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)
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':
112 elif args
[0] == 'l1':
114 elif len(args
) == 2 and args
[0] == 'l2':
115 offset
= int(args
[1])
116 print(qed
.read_table(offset
))
118 err('unrecognized sub-command')
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']
125 elif table_level
== 'l2':
126 _
, offset
= random_table_item(qed
.l1_table
)
127 table
= qed
.read_table(offset
)
129 err('unrecognized sub-command')
130 corrupt_table_duplicate(table
)
131 qed
.write_table(offset
, table
)
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']
138 elif table_level
== 'l2':
139 _
, offset
= random_table_item(qed
.l1_table
)
140 table
= qed
.read_table(offset
)
142 err('unrecognized sub-command')
143 corrupt_table_invalidate(qed
, table
)
144 qed
.write_table(offset
, table
)
146 def cmd_need_check(qed
, *args
):
147 '''need-check [on|off] - Test, set, or clear the QED_F_NEED_CHECK header bit'''
149 print(bool(qed
.header
['features'] & QED_F_NEED_CHECK
))
153 qed
.header
['features'] |
= QED_F_NEED_CHECK
154 elif args
[0] == 'off':
155 qed
.header
['features'] &= ~QED_F_NEED_CHECK
157 err('unrecognized sub-command')
160 def cmd_zero_cluster(qed
, pos
, *args
):
161 '''zero-cluster <pos> [<n>] - Zero data clusters'''
165 err('expected one argument')
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')
173 l2_offset
= qed
.l1_table
[l1_index
]
174 l2_table
= qed
.read_table(l2_offset
)
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']
181 def cmd_copy_metadata(qed
, outfile
):
182 '''copy-metadata <outfile> - Copy metadata only (for scrubbing corrupted images)'''
183 out
= open(outfile
, 'wb')
186 out
.seek(qed
.filesize
- 1)
189 # Copy header clusters
191 header_size_bytes
= qed
.header
['header_size'] * qed
.header
['cluster_size']
192 out
.write(qed
.raw_pread(0, header_size_bytes
))
195 out
.seek(qed
.header
['l1_table_offset'])
196 s
= ''.join(pack_table_elem(x
) for x
in qed
.l1_table
)
200 for l2_offset
in qed
.l1_table
:
203 l2_table
= qed
.read_table(l2_offset
)
205 s
= ''.join(pack_table_elem(x
) for x
in l2_table
)
211 print('Usage: %s <file> <cmd> [<arg>, ...]' % sys
.argv
[0])
213 print('Supported commands:')
214 for cmd
in sorted(x
for x
in globals() if x
.startswith('cmd_')):
215 print(globals()[cmd
].__doc
__)
219 if len(sys
.argv
) < 3:
221 filename
, cmd
= sys
.argv
[1:3]
223 cmd
= 'cmd_' + cmd
.replace('-', '_')
224 if cmd
not in globals():
227 qed
= QED(open(filename
, 'r+b'))
229 globals()[cmd
](qed
, *sys
.argv
[3:])
230 except TypeError as e
:
231 sys
.stderr
.write(globals()[cmd
].__doc
__ + '\n')
234 if __name__
== '__main__':